// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package com.google.protobuf;

import static com.google.protobuf.ArrayDecoders.decodeBoolList;
import static com.google.protobuf.ArrayDecoders.decodeBytes;
import static com.google.protobuf.ArrayDecoders.decodeBytesList;
import static com.google.protobuf.ArrayDecoders.decodeDouble;
import static com.google.protobuf.ArrayDecoders.decodeDoubleList;
import static com.google.protobuf.ArrayDecoders.decodeExtensionOrUnknownField;
import static com.google.protobuf.ArrayDecoders.decodeFixed32;
import static com.google.protobuf.ArrayDecoders.decodeFixed32List;
import static com.google.protobuf.ArrayDecoders.decodeFixed64;
import static com.google.protobuf.ArrayDecoders.decodeFixed64List;
import static com.google.protobuf.ArrayDecoders.decodeFloat;
import static com.google.protobuf.ArrayDecoders.decodeFloatList;
import static com.google.protobuf.ArrayDecoders.decodeGroupField;
import static com.google.protobuf.ArrayDecoders.decodeGroupList;
import static com.google.protobuf.ArrayDecoders.decodeMessageField;
import static com.google.protobuf.ArrayDecoders.decodeMessageList;
import static com.google.protobuf.ArrayDecoders.decodePackedBoolList;
import static com.google.protobuf.ArrayDecoders.decodePackedDoubleList;
import static com.google.protobuf.ArrayDecoders.decodePackedFixed32List;
import static com.google.protobuf.ArrayDecoders.decodePackedFixed64List;
import static com.google.protobuf.ArrayDecoders.decodePackedFloatList;
import static com.google.protobuf.ArrayDecoders.decodePackedSInt32List;
import static com.google.protobuf.ArrayDecoders.decodePackedSInt64List;
import static com.google.protobuf.ArrayDecoders.decodePackedVarint32List;
import static com.google.protobuf.ArrayDecoders.decodePackedVarint64List;
import static com.google.protobuf.ArrayDecoders.decodeSInt32List;
import static com.google.protobuf.ArrayDecoders.decodeSInt64List;
import static com.google.protobuf.ArrayDecoders.decodeString;
import static com.google.protobuf.ArrayDecoders.decodeStringList;
import static com.google.protobuf.ArrayDecoders.decodeStringListRequireUtf8;
import static com.google.protobuf.ArrayDecoders.decodeStringRequireUtf8;
import static com.google.protobuf.ArrayDecoders.decodeUnknownField;
import static com.google.protobuf.ArrayDecoders.decodeVarint32;
import static com.google.protobuf.ArrayDecoders.decodeVarint32List;
import static com.google.protobuf.ArrayDecoders.decodeVarint64;
import static com.google.protobuf.ArrayDecoders.decodeVarint64List;
import static com.google.protobuf.ArrayDecoders.skipField;

import com.google.protobuf.ArrayDecoders.Registers;
import com.google.protobuf.ByteString.CodedBuilder;
import com.google.protobuf.FieldSet.FieldDescriptorLite;
import com.google.protobuf.Internal.EnumVerifier;
import com.google.protobuf.Internal.ProtobufList;
import com.google.protobuf.MapEntryLite.Metadata;

import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Schema used for standard messages.
 */
final class MessageSchema<T> implements Schema<T> {
    private static final int INTS_PER_FIELD = 3;
    private static final int OFFSET_BITS = 20;
    private static final int OFFSET_MASK = 0XFFFFF;
    private static final int FIELD_TYPE_MASK = 0x0FF00000;
    private static final int REQUIRED_MASK = 0x10000000;
    private static final int ENFORCE_UTF8_MASK = 0x20000000;
    private static final int NO_PRESENCE_SENTINEL = -1 & OFFSET_MASK;
    private static final int[] EMPTY_INT_ARRAY = new int[0];

    /**
     * An offset applied to the field type ID for scalar fields that are a member of a oneof.
     */
    static final int ONEOF_TYPE_OFFSET = 51 /* FieldType.MAP + 1 */;

    /**
     * Keep a direct reference to the unsafe object so we don't need to go through the UnsafeUtil
     * wrapper for every call.
     */
    private static final sun.misc.Unsafe UNSAFE = UnsafeUtil.getUnsafe();

    /**
     * Holds all information for accessing the message fields. The layout is as follows (field
     * positions are relative to the offset of the start of the field in the buffer):
     *
     * <p>
     *
     * <pre>
     * [ 0 -    3] unused
     * [ 4 -   31] field number
     * [32 -   33] unused
     * [34 -   34] whether UTF-8 enforcement should be applied to a string field.
     * [35 -   35] whether the field is required
     * [36 -   43] field type / for oneof: field type + {@link #ONEOF_TYPE_OFFSET}
     * [44 -   63] field offset / oneof value field offset
     * [64 -   69] unused
     * [70 -   75] field presence mask shift (unused for oneof/repeated fields)
     * [76 -   95] presence field offset / oneof case field offset / cached size field offset
     * </pre>
     * <p>
     * Note that presence field offset can only use 20 bits - 1. All bits set to 1 is the sentinel
     * value for non-presence. This is not validated at runtime, we simply assume message layouts
     * will not exceed 1MB (assuming ~10 bytes per field, that implies 100k fields which should hit
     * other javac limits first).
     */
    private final int[] buffer;

    /**
     * Holds object references for fields. For each field entry in {@code buffer}, there are two
     * corresponding entries in this array. The content is different from different field types:
     *
     * <pre>
     *   Map fields:
     *     objects[pos] = map default entry instance
     *     objects[pos + 1] = EnumVerifier if map value is enum, or message class reference if map
     *                        value is message.
     *   Message fields:
     *     objects[pos] = null or cached message schema
     *     objects[pos + 1] = message class reference
     *   Enum fields:
     *     objects[pos] = null
     *     objects[pos + 1] = EnumVerifier
     * </pre>
     */
    private final Object[] objects;

    private final int minFieldNumber;
    private final int maxFieldNumber;

    private final MessageLite defaultInstance;
    private final boolean hasExtensions;
    private final boolean lite;
    private final boolean proto3;
    // TODO(xiaofeng): Make both full-runtime and lite-runtime support cached field size.
    private final boolean useCachedSizeField;

    /**
     * Represents [checkInitialized positions, map field positions, repeated field offsets].
     */
    private final int[] intArray;

    /**
     * Values at indices 0 -> checkInitializedCount in intArray are positions to check for
     * initialization.
     */
    private final int checkInitializedCount;

    /**
     * Values at indices checkInitializedCount -> repeatedFieldOffsetStart are map positions.
     * Everything after that are repeated field offsets.
     */
    private final int repeatedFieldOffsetStart;

    private final NewInstanceSchema newInstanceSchema;
    private final ListFieldSchema listFieldSchema;
    private final UnknownFieldSchema<?, ?> unknownFieldSchema;
    private final ExtensionSchema<?> extensionSchema;
    private final MapFieldSchema mapFieldSchema;

    private MessageSchema(
            int[] buffer,
            Object[] objects,
            int minFieldNumber,
            int maxFieldNumber,
            MessageLite defaultInstance,
            boolean proto3,
            boolean useCachedSizeField,
            int[] intArray,
            int checkInitialized,
            int mapFieldPositions,
            NewInstanceSchema newInstanceSchema,
            ListFieldSchema listFieldSchema,
            UnknownFieldSchema<?, ?> unknownFieldSchema,
            ExtensionSchema<?> extensionSchema,
            MapFieldSchema mapFieldSchema) {
        this.buffer = buffer;
        this.objects = objects;
        this.minFieldNumber = minFieldNumber;
        this.maxFieldNumber = maxFieldNumber;

        this.lite = defaultInstance instanceof GeneratedMessageLite;
        this.proto3 = proto3;
        this.hasExtensions = extensionSchema != null && extensionSchema.hasExtensions(defaultInstance);
        this.useCachedSizeField = useCachedSizeField;

        this.intArray = intArray;
        this.checkInitializedCount = checkInitialized;
        this.repeatedFieldOffsetStart = mapFieldPositions;

        this.newInstanceSchema = newInstanceSchema;
        this.listFieldSchema = listFieldSchema;
        this.unknownFieldSchema = unknownFieldSchema;
        this.extensionSchema = extensionSchema;
        this.defaultInstance = defaultInstance;
        this.mapFieldSchema = mapFieldSchema;
    }

    static <T> MessageSchema<T> newSchema(
            Class<T> messageClass,
            MessageInfo messageInfo,
            NewInstanceSchema newInstanceSchema,
            ListFieldSchema listFieldSchema,
            UnknownFieldSchema<?, ?> unknownFieldSchema,
            ExtensionSchema<?> extensionSchema,
            MapFieldSchema mapFieldSchema) {
        if (messageInfo instanceof RawMessageInfo) {
            return newSchemaForRawMessageInfo(
                    (RawMessageInfo) messageInfo,
                    newInstanceSchema,
                    listFieldSchema,
                    unknownFieldSchema,
                    extensionSchema,
                    mapFieldSchema);

        } else {
            return newSchemaForMessageInfo(
                    (StructuralMessageInfo) messageInfo,
                    newInstanceSchema,
                    listFieldSchema,
                    unknownFieldSchema,
                    extensionSchema,
                    mapFieldSchema);
        }
    }

    static <T> MessageSchema<T> newSchemaForRawMessageInfo(
            RawMessageInfo messageInfo,
            NewInstanceSchema newInstanceSchema,
            ListFieldSchema listFieldSchema,
            UnknownFieldSchema<?, ?> unknownFieldSchema,
            ExtensionSchema<?> extensionSchema,
            MapFieldSchema mapFieldSchema) {
        final boolean isProto3 = messageInfo.getSyntax() == ProtoSyntax.PROTO3;

        String info = messageInfo.getStringInfo();
        final int length = info.length();
        int i = 0;

        int next = info.charAt(i++);
        if (next >= 0xD800) {
            int result = next & 0x1FFF;
            int shift = 13;
            while ((next = info.charAt(i++)) >= 0xD800) {
                result |= (next & 0x1FFF) << shift;
                shift += 13;
            }
            next = result | (next << shift);
        }
        final int unusedFlags = next;

        next = info.charAt(i++);
        if (next >= 0xD800) {
            int result = next & 0x1FFF;
            int shift = 13;
            while ((next = info.charAt(i++)) >= 0xD800) {
                result |= (next & 0x1FFF) << shift;
                shift += 13;
            }
            next = result | (next << shift);
        }
        final int fieldCount = next;

        final int oneofCount;
        final int hasBitsCount;
        final int minFieldNumber;
        final int maxFieldNumber;
        final int numEntries;
        final int mapFieldCount;
        final int repeatedFieldCount;
        final int checkInitialized;
        final int[] intArray;
        int objectsPosition;
        if (fieldCount == 0) {
            oneofCount = 0;
            hasBitsCount = 0;
            minFieldNumber = 0;
            maxFieldNumber = 0;
            numEntries = 0;
            mapFieldCount = 0;
            repeatedFieldCount = 0;
            checkInitialized = 0;
            intArray = EMPTY_INT_ARRAY;
            objectsPosition = 0;
        } else {
            next = info.charAt(i++);
            if (next >= 0xD800) {
                int result = next & 0x1FFF;
                int shift = 13;
                while ((next = info.charAt(i++)) >= 0xD800) {
                    result |= (next & 0x1FFF) << shift;
                    shift += 13;
                }
                next = result | (next << shift);
            }
            oneofCount = next;

            next = info.charAt(i++);
            if (next >= 0xD800) {
                int result = next & 0x1FFF;
                int shift = 13;
                while ((next = info.charAt(i++)) >= 0xD800) {
                    result |= (next & 0x1FFF) << shift;
                    shift += 13;
                }
                next = result | (next << shift);
            }
            hasBitsCount = next;

            next = info.charAt(i++);
            if (next >= 0xD800) {
                int result = next & 0x1FFF;
                int shift = 13;
                while ((next = info.charAt(i++)) >= 0xD800) {
                    result |= (next & 0x1FFF) << shift;
                    shift += 13;
                }
                next = result | (next << shift);
            }
            minFieldNumber = next;

            next = info.charAt(i++);
            if (next >= 0xD800) {
                int result = next & 0x1FFF;
                int shift = 13;
                while ((next = info.charAt(i++)) >= 0xD800) {
                    result |= (next & 0x1FFF) << shift;
                    shift += 13;
                }
                next = result | (next << shift);
            }
            maxFieldNumber = next;

            next = info.charAt(i++);
            if (next >= 0xD800) {
                int result = next & 0x1FFF;
                int shift = 13;
                while ((next = info.charAt(i++)) >= 0xD800) {
                    result |= (next & 0x1FFF) << shift;
                    shift += 13;
                }
                next = result | (next << shift);
            }
            numEntries = next;

            next = info.charAt(i++);
            if (next >= 0xD800) {
                int result = next & 0x1FFF;
                int shift = 13;
                while ((next = info.charAt(i++)) >= 0xD800) {
                    result |= (next & 0x1FFF) << shift;
                    shift += 13;
                }
                next = result | (next << shift);
            }
            mapFieldCount = next;

            next = info.charAt(i++);
            if (next >= 0xD800) {
                int result = next & 0x1FFF;
                int shift = 13;
                while ((next = info.charAt(i++)) >= 0xD800) {
                    result |= (next & 0x1FFF) << shift;
                    shift += 13;
                }
                next = result | (next << shift);
            }
            repeatedFieldCount = next;

            next = info.charAt(i++);
            if (next >= 0xD800) {
                int result = next & 0x1FFF;
                int shift = 13;
                while ((next = info.charAt(i++)) >= 0xD800) {
                    result |= (next & 0x1FFF) << shift;
                    shift += 13;
                }
                next = result | (next << shift);
            }
            checkInitialized = next;
            intArray = new int[checkInitialized + mapFieldCount + repeatedFieldCount];
            // Field objects are after a list of (oneof, oneofCase) pairs  + a list of hasbits fields.
            objectsPosition = oneofCount * 2 + hasBitsCount;
        }

        final sun.misc.Unsafe unsafe = UNSAFE;
        final Object[] messageInfoObjects = messageInfo.getObjects();
        int checkInitializedPosition = 0;
        final Class<?> messageClass = messageInfo.getDefaultInstance().getClass();
        int[] buffer = new int[numEntries * INTS_PER_FIELD];
        Object[] objects = new Object[numEntries * 2];

        int mapFieldIndex = checkInitialized;
        int repeatedFieldIndex = checkInitialized + mapFieldCount;

        int bufferIndex = 0;
        while (i < length) {
            final int fieldNumber;
            final int fieldTypeWithExtraBits;
            final int fieldType;

            next = info.charAt(i++);
            if (next >= 0xD800) {
                int result = next & 0x1FFF;
                int shift = 13;
                while ((next = info.charAt(i++)) >= 0xD800) {
                    result |= (next & 0x1FFF) << shift;
                    shift += 13;
                }
                next = result | (next << shift);
            }
            fieldNumber = next;

            next = info.charAt(i++);
            if (next >= 0xD800) {
                int result = next & 0x1FFF;
                int shift = 13;
                while ((next = info.charAt(i++)) >= 0xD800) {
                    result |= (next & 0x1FFF) << shift;
                    shift += 13;
                }
                next = result | (next << shift);
            }
            fieldTypeWithExtraBits = next;
            fieldType = fieldTypeWithExtraBits & 0xFF;

            if ((fieldTypeWithExtraBits & 0x400) != 0) {
                intArray[checkInitializedPosition++] = bufferIndex;
            }

            final int fieldOffset;
            final int presenceMaskShift;
            final int presenceFieldOffset;

            // Oneof
            if (fieldType >= ONEOF_TYPE_OFFSET) {
                next = info.charAt(i++);
                if (next >= 0xD800) {
                    int result = next & 0x1FFF;
                    int shift = 13;
                    while ((next = info.charAt(i++)) >= 0xD800) {
                        result |= (next & 0x1FFF) << shift;
                        shift += 13;
                    }
                    next = result | (next << shift);
                }
                int oneofIndex = next;

                final int oneofFieldType = fieldType - ONEOF_TYPE_OFFSET;
                if (oneofFieldType == 9 /* FieldType.MESSAGE */
                        || oneofFieldType == 17 /* FieldType.GROUP */) {
                    objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageInfoObjects[objectsPosition++];
                } else if (oneofFieldType == 12 /* FieldType.ENUM */) {
                    if (!isProto3) {
                        objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageInfoObjects[objectsPosition++];
                    }
                }

                final java.lang.reflect.Field oneofField;
                int index = oneofIndex * 2;
                Object o = messageInfoObjects[index];
                if (o instanceof java.lang.reflect.Field) {
                    oneofField = (java.lang.reflect.Field) o;
                } else {
                    oneofField = reflectField(messageClass, (String) o);
                    // Memoize java.lang.reflect.Field instances for oneof/hasbits fields, since they're
                    // potentially used for many Protobuf fields.  Since there's a 1-1 mapping from the
                    // Protobuf field to the Java Field for non-oneofs, there's no benefit for memoizing
                    // those.
                    messageInfoObjects[index] = oneofField;
                }

                fieldOffset = (int) unsafe.objectFieldOffset(oneofField);

                final java.lang.reflect.Field oneofCaseField;
                index++;
                o = messageInfoObjects[index];
                if (o instanceof java.lang.reflect.Field) {
                    oneofCaseField = (java.lang.reflect.Field) o;
                } else {
                    oneofCaseField = reflectField(messageClass, (String) o);
                    messageInfoObjects[index] = oneofCaseField;
                }

                presenceFieldOffset = (int) unsafe.objectFieldOffset(oneofCaseField);
                presenceMaskShift = 0;
            } else {
                Field field = reflectField(messageClass, (String) messageInfoObjects[objectsPosition++]);
                if (fieldType == 9 /* FieldType.MESSAGE */ || fieldType == 17 /* FieldType.GROUP */) {
                    objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = field.getType();
                } else if (fieldType == 27 /* FieldType.MESSAGE_LIST */
                        || fieldType == 49 /* FieldType.GROUP_LIST */) {
                    objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageInfoObjects[objectsPosition++];
                } else if (fieldType == 12 /* FieldType.ENUM */
                        || fieldType == 30 /* FieldType.ENUM_LIST */
                        || fieldType == 44 /* FieldType.ENUM_LIST_PACKED */) {
                    if (!isProto3) {
                        objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageInfoObjects[objectsPosition++];
                    }
                } else if (fieldType == 50 /* FieldType.MAP */) {
                    intArray[mapFieldIndex++] = bufferIndex;
                    objects[bufferIndex / INTS_PER_FIELD * 2] = messageInfoObjects[objectsPosition++];
                    if ((fieldTypeWithExtraBits & 0x800) != 0) {
                        objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageInfoObjects[objectsPosition++];
                    }
                }

                fieldOffset = (int) unsafe.objectFieldOffset(field);
                boolean hasHasBit = (fieldTypeWithExtraBits & 0x1000) == 0x1000;
                if (hasHasBit && fieldType <= 17 /* FieldType.GROUP */) {
                    next = info.charAt(i++);
                    if (next >= 0xD800) {
                        int result = next & 0x1FFF;
                        int shift = 13;
                        while ((next = info.charAt(i++)) >= 0xD800) {
                            result |= (next & 0x1FFF) << shift;
                            shift += 13;
                        }
                        next = result | (next << shift);
                    }
                    int hasBitsIndex = next;

                    final java.lang.reflect.Field hasBitsField;
                    int index = oneofCount * 2 + hasBitsIndex / 32;
                    Object o = messageInfoObjects[index];
                    if (o instanceof java.lang.reflect.Field) {
                        hasBitsField = (java.lang.reflect.Field) o;
                    } else {
                        hasBitsField = reflectField(messageClass, (String) o);
                        messageInfoObjects[index] = hasBitsField;
                    }

                    presenceFieldOffset = (int) unsafe.objectFieldOffset(hasBitsField);
                    presenceMaskShift = hasBitsIndex % 32;
                } else {
                    presenceFieldOffset = NO_PRESENCE_SENTINEL;
                    presenceMaskShift = 0;
                }

                if (fieldType >= 18 && fieldType <= 49) {
                    // Field types of repeated fields are in a consecutive range from 18 (DOUBLE_LIST) to
                    // 49 (GROUP_LIST).
                    intArray[repeatedFieldIndex++] = fieldOffset;
                }
            }

            buffer[bufferIndex++] = fieldNumber;
            buffer[bufferIndex++] =
                    ((fieldTypeWithExtraBits & 0x200) != 0 ? ENFORCE_UTF8_MASK : 0)
                            | ((fieldTypeWithExtraBits & 0x100) != 0 ? REQUIRED_MASK : 0)
                            | (fieldType << OFFSET_BITS)
                            | fieldOffset;
            buffer[bufferIndex++] = (presenceMaskShift << OFFSET_BITS) | presenceFieldOffset;
        }

        return new MessageSchema<T>(
                buffer,
                objects,
                minFieldNumber,
                maxFieldNumber,
                messageInfo.getDefaultInstance(),
                isProto3,
                /* useCachedSizeField= */ false,
                intArray,
                checkInitialized,
                checkInitialized + mapFieldCount,
                newInstanceSchema,
                listFieldSchema,
                unknownFieldSchema,
                extensionSchema,
                mapFieldSchema);
    }

    private static java.lang.reflect.Field reflectField(Class<?> messageClass, String fieldName) {
        try {
            return messageClass.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            // Some Samsung devices lie about what fields are present via the getDeclaredField API so
            // we do the for loop properly that they seem to have messed up...
            java.lang.reflect.Field[] fields = messageClass.getDeclaredFields();
            for (java.lang.reflect.Field field : fields) {
                if (fieldName.equals(field.getName())) {
                    return field;
                }
            }

            // If we make it here, the runtime still lies about what we know to be true at compile
            // time. We throw to alert server monitoring for further remediation.
            throw new RuntimeException(
                    "Field "
                            + fieldName
                            + " for "
                            + messageClass.getName()
                            + " not found. Known fields are "
                            + Arrays.toString(fields));
        }
    }

    static <T> MessageSchema<T> newSchemaForMessageInfo(
            StructuralMessageInfo messageInfo,
            NewInstanceSchema newInstanceSchema,
            ListFieldSchema listFieldSchema,
            UnknownFieldSchema<?, ?> unknownFieldSchema,
            ExtensionSchema<?> extensionSchema,
            MapFieldSchema mapFieldSchema) {
        final boolean isProto3 = messageInfo.getSyntax() == ProtoSyntax.PROTO3;
        FieldInfo[] fis = messageInfo.getFields();
        final int minFieldNumber;
        final int maxFieldNumber;
        if (fis.length == 0) {
            minFieldNumber = 0;
            maxFieldNumber = 0;
        } else {
            minFieldNumber = fis[0].getFieldNumber();
            maxFieldNumber = fis[fis.length - 1].getFieldNumber();
        }

        final int numEntries = fis.length;

        int[] buffer = new int[numEntries * INTS_PER_FIELD];
        Object[] objects = new Object[numEntries * 2];

        int mapFieldCount = 0;
        int repeatedFieldCount = 0;
        for (FieldInfo fi : fis) {
            if (fi.getType() == FieldType.MAP) {
                mapFieldCount++;
            } else if (fi.getType().id() >= 18 && fi.getType().id() <= 49) {
                // Field types of repeated fields are in a consecutive range from 18 (DOUBLE_LIST) to
                // 49 (GROUP_LIST).
                repeatedFieldCount++;
            }
        }
        int[] mapFieldPositions = mapFieldCount > 0 ? new int[mapFieldCount] : null;
        int[] repeatedFieldOffsets = repeatedFieldCount > 0 ? new int[repeatedFieldCount] : null;
        mapFieldCount = 0;
        repeatedFieldCount = 0;

        int[] checkInitialized = messageInfo.getCheckInitialized();
        if (checkInitialized == null) {
            checkInitialized = EMPTY_INT_ARRAY;
        }
        int checkInitializedIndex = 0;
        // Fill in the manifest data from the descriptors.
        int fieldIndex = 0;
        for (int bufferIndex = 0; fieldIndex < fis.length; bufferIndex += INTS_PER_FIELD) {
            final FieldInfo fi = fis[fieldIndex];
            final int fieldNumber = fi.getFieldNumber();

            // We found the entry for the next field. Store the entry in the manifest for
            // this field and increment the field index.
            storeFieldData(fi, buffer, bufferIndex, objects);

            // Convert field number to index
            if (checkInitializedIndex < checkInitialized.length
                    && checkInitialized[checkInitializedIndex] == fieldNumber) {
                checkInitialized[checkInitializedIndex++] = bufferIndex;
            }

            if (fi.getType() == FieldType.MAP) {
                mapFieldPositions[mapFieldCount++] = bufferIndex;
            } else if (fi.getType().id() >= 18 && fi.getType().id() <= 49) {
                // Field types of repeated fields are in a consecutive range from 18 (DOUBLE_LIST) to
                // 49 (GROUP_LIST).
                repeatedFieldOffsets[repeatedFieldCount++] =
                        (int) UnsafeUtil.objectFieldOffset(fi.getField());
            }

            fieldIndex++;
        }

        if (mapFieldPositions == null) {
            mapFieldPositions = EMPTY_INT_ARRAY;
        }
        if (repeatedFieldOffsets == null) {
            repeatedFieldOffsets = EMPTY_INT_ARRAY;
        }
        int[] combined =
                new int[checkInitialized.length + mapFieldPositions.length + repeatedFieldOffsets.length];
        System.arraycopy(checkInitialized, 0, combined, 0, checkInitialized.length);
        System.arraycopy(
                mapFieldPositions, 0, combined, checkInitialized.length, mapFieldPositions.length);
        System.arraycopy(
                repeatedFieldOffsets,
                0,
                combined,
                checkInitialized.length + mapFieldPositions.length,
                repeatedFieldOffsets.length);

        return new MessageSchema<T>(
                buffer,
                objects,
                minFieldNumber,
                maxFieldNumber,
                messageInfo.getDefaultInstance(),
                isProto3,
                /* useCachedSizeField= */ true,
                combined,
                checkInitialized.length,
                checkInitialized.length + mapFieldPositions.length,
                newInstanceSchema,
                listFieldSchema,
                unknownFieldSchema,
                extensionSchema,
                mapFieldSchema);
    }

    private static void storeFieldData(
            FieldInfo fi, int[] buffer, int bufferIndex, Object[] objects) {
        final int fieldOffset;
        final int typeId;
        final int presenceMaskShift;
        final int presenceFieldOffset;

        OneofInfo oneof = fi.getOneof();
        if (oneof != null) {
            typeId = fi.getType().id() + ONEOF_TYPE_OFFSET;
            fieldOffset = (int) UnsafeUtil.objectFieldOffset(oneof.getValueField());
            presenceFieldOffset = (int) UnsafeUtil.objectFieldOffset(oneof.getCaseField());
            presenceMaskShift = 0;
        } else {
            FieldType type = fi.getType();
            fieldOffset = (int) UnsafeUtil.objectFieldOffset(fi.getField());
            typeId = type.id();
            if (!type.isList() && !type.isMap()) {
                Field presenceField = fi.getPresenceField();
                if (presenceField == null) {
                    presenceFieldOffset = NO_PRESENCE_SENTINEL;
                } else {
                    presenceFieldOffset = (int) UnsafeUtil.objectFieldOffset(presenceField);
                }
                presenceMaskShift = Integer.numberOfTrailingZeros(fi.getPresenceMask());
            } else {
                if (fi.getCachedSizeField() == null) {
                    presenceFieldOffset = 0;
                    presenceMaskShift = 0;
                } else {
                    presenceFieldOffset = (int) UnsafeUtil.objectFieldOffset(fi.getCachedSizeField());
                    presenceMaskShift = 0;
                }
            }
        }

        buffer[bufferIndex] = fi.getFieldNumber();
        buffer[bufferIndex + 1] =
                (fi.isEnforceUtf8() ? ENFORCE_UTF8_MASK : 0)
                        | (fi.isRequired() ? REQUIRED_MASK : 0)
                        | (typeId << OFFSET_BITS)
                        | fieldOffset;
        buffer[bufferIndex + 2] = (presenceMaskShift << OFFSET_BITS) | presenceFieldOffset;

        Object messageFieldClass = fi.getMessageFieldClass();
        if (fi.getMapDefaultEntry() != null) {
            objects[bufferIndex / INTS_PER_FIELD * 2] = fi.getMapDefaultEntry();
            if (messageFieldClass != null) {
                objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageFieldClass;
            } else if (fi.getEnumVerifier() != null) {
                objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = fi.getEnumVerifier();
            }
        } else {
            if (messageFieldClass != null) {
                objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageFieldClass;
            } else if (fi.getEnumVerifier() != null) {
                objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = fi.getEnumVerifier();
            }
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public T newInstance() {
        return (T) newInstanceSchema.newInstance(defaultInstance);
    }

    @Override
    public boolean equals(T message, T other) {
        final int bufferLength = buffer.length;
        for (int pos = 0; pos < bufferLength; pos += INTS_PER_FIELD) {
            if (!equals(message, other, pos)) {
                return false;
            }
        }

        Object messageUnknown = unknownFieldSchema.getFromMessage(message);
        Object otherUnknown = unknownFieldSchema.getFromMessage(other);
        if (!messageUnknown.equals(otherUnknown)) {
            return false;
        }

        if (hasExtensions) {
            FieldSet<?> messageExtensions = extensionSchema.getExtensions(message);
            FieldSet<?> otherExtensions = extensionSchema.getExtensions(other);
            return messageExtensions.equals(otherExtensions);
        }
        return true;
    }

    private boolean equals(T message, T other, int pos) {
        final int typeAndOffset = typeAndOffsetAt(pos);
        final long offset = offset(typeAndOffset);

        switch (type(typeAndOffset)) {
            case 0: // DOUBLE:
                return arePresentForEquals(message, other, pos)
                        && Double.doubleToLongBits(UnsafeUtil.getDouble(message, offset))
                        == Double.doubleToLongBits(UnsafeUtil.getDouble(other, offset));
            case 1: // FLOAT:
                return arePresentForEquals(message, other, pos)
                        && Float.floatToIntBits(UnsafeUtil.getFloat(message, offset))
                        == Float.floatToIntBits(UnsafeUtil.getFloat(other, offset));
            case 2: // INT64:
                return arePresentForEquals(message, other, pos)
                        && UnsafeUtil.getLong(message, offset) == UnsafeUtil.getLong(other, offset);
            case 3: // UINT64:
                return arePresentForEquals(message, other, pos)
                        && UnsafeUtil.getLong(message, offset) == UnsafeUtil.getLong(other, offset);
            case 4: // INT32:
                return arePresentForEquals(message, other, pos)
                        && UnsafeUtil.getInt(message, offset) == UnsafeUtil.getInt(other, offset);
            case 5: // FIXED64:
                return arePresentForEquals(message, other, pos)
                        && UnsafeUtil.getLong(message, offset) == UnsafeUtil.getLong(other, offset);
            case 6: // FIXED32:
                return arePresentForEquals(message, other, pos)
                        && UnsafeUtil.getInt(message, offset) == UnsafeUtil.getInt(other, offset);
            case 7: // BOOL:
                return arePresentForEquals(message, other, pos)
                        && UnsafeUtil.getBoolean(message, offset) == UnsafeUtil.getBoolean(other, offset);
            case 8: // STRING:
                return arePresentForEquals(message, other, pos)
                        && SchemaUtil.safeEquals(
                        UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));
            case 9: // MESSAGE:
                return arePresentForEquals(message, other, pos)
                        && SchemaUtil.safeEquals(
                        UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));
            case 10: // BYTES:
                return arePresentForEquals(message, other, pos)
                        && SchemaUtil.safeEquals(
                        UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));
            case 11: // UINT32:
                return arePresentForEquals(message, other, pos)
                        && UnsafeUtil.getInt(message, offset) == UnsafeUtil.getInt(other, offset);
            case 12: // ENUM:
                return arePresentForEquals(message, other, pos)
                        && UnsafeUtil.getInt(message, offset) == UnsafeUtil.getInt(other, offset);
            case 13: // SFIXED32:
                return arePresentForEquals(message, other, pos)
                        && UnsafeUtil.getInt(message, offset) == UnsafeUtil.getInt(other, offset);
            case 14: // SFIXED64:
                return arePresentForEquals(message, other, pos)
                        && UnsafeUtil.getLong(message, offset) == UnsafeUtil.getLong(other, offset);
            case 15: // SINT32:
                return arePresentForEquals(message, other, pos)
                        && UnsafeUtil.getInt(message, offset) == UnsafeUtil.getInt(other, offset);
            case 16: // SINT64:
                return arePresentForEquals(message, other, pos)
                        && UnsafeUtil.getLong(message, offset) == UnsafeUtil.getLong(other, offset);
            case 17: // GROUP:
                return arePresentForEquals(message, other, pos)
                        && SchemaUtil.safeEquals(
                        UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));

            case 18: // DOUBLE_LIST:
            case 19: // FLOAT_LIST:
            case 20: // INT64_LIST:
            case 21: // UINT64_LIST:
            case 22: // INT32_LIST:
            case 23: // FIXED64_LIST:
            case 24: // FIXED32_LIST:
            case 25: // BOOL_LIST:
            case 26: // STRING_LIST:
            case 27: // MESSAGE_LIST:
            case 28: // BYTES_LIST:
            case 29: // UINT32_LIST:
            case 30: // ENUM_LIST:
            case 31: // SFIXED32_LIST:
            case 32: // SFIXED64_LIST:
            case 33: // SINT32_LIST:
            case 34: // SINT64_LIST:
            case 35: // DOUBLE_LIST_PACKED:
            case 36: // FLOAT_LIST_PACKED:
            case 37: // INT64_LIST_PACKED:
            case 38: // UINT64_LIST_PACKED:
            case 39: // INT32_LIST_PACKED:
            case 40: // FIXED64_LIST_PACKED:
            case 41: // FIXED32_LIST_PACKED:
            case 42: // BOOL_LIST_PACKED:
            case 43: // UINT32_LIST_PACKED:
            case 44: // ENUM_LIST_PACKED:
            case 45: // SFIXED32_LIST_PACKED:
            case 46: // SFIXED64_LIST_PACKED:
            case 47: // SINT32_LIST_PACKED:
            case 48: // SINT64_LIST_PACKED:
            case 49: // GROUP_LIST:
                return SchemaUtil.safeEquals(
                        UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));
            case 50: // MAP:
                return SchemaUtil.safeEquals(
                        UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));
            case 51: // ONEOF_DOUBLE:
            case 52: // ONEOF_FLOAT:
            case 53: // ONEOF_INT64:
            case 54: // ONEOF_UINT64:
            case 55: // ONEOF_INT32:
            case 56: // ONEOF_FIXED64:
            case 57: // ONEOF_FIXED32:
            case 58: // ONEOF_BOOL:
            case 59: // ONEOF_STRING:
            case 60: // ONEOF_MESSAGE:
            case 61: // ONEOF_BYTES:
            case 62: // ONEOF_UINT32:
            case 63: // ONEOF_ENUM:
            case 64: // ONEOF_SFIXED32:
            case 65: // ONEOF_SFIXED64:
            case 66: // ONEOF_SINT32:
            case 67: // ONEOF_SINT64:
            case 68: // ONEOF_GROUP:
                return isOneofCaseEqual(message, other, pos)
                        && SchemaUtil.safeEquals(
                        UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));
            default:
                // Assume it's an empty entry - just go to the next entry.
                return true;
        }
    }

    @Override
    public int hashCode(T message) {
        int hashCode = 0;
        final int bufferLength = buffer.length;
        for (int pos = 0; pos < bufferLength; pos += INTS_PER_FIELD) {
            final int typeAndOffset = typeAndOffsetAt(pos);
            final int entryNumber = numberAt(pos);

            final long offset = offset(typeAndOffset);

            switch (type(typeAndOffset)) {
                case 0: // DOUBLE:
                    hashCode =
                            (hashCode * 53)
                                    + Internal.hashLong(
                                    Double.doubleToLongBits(UnsafeUtil.getDouble(message, offset)));
                    break;
                case 1: // FLOAT:
                    hashCode = (hashCode * 53) + Float.floatToIntBits(UnsafeUtil.getFloat(message, offset));
                    break;
                case 2: // INT64:
                    hashCode = (hashCode * 53) + Internal.hashLong(UnsafeUtil.getLong(message, offset));
                    break;
                case 3: // UINT64:
                    hashCode = (hashCode * 53) + Internal.hashLong(UnsafeUtil.getLong(message, offset));
                    break;
                case 4: // INT32:
                    hashCode = (hashCode * 53) + (UnsafeUtil.getInt(message, offset));
                    break;
                case 5: // FIXED64:
                    hashCode = (hashCode * 53) + Internal.hashLong(UnsafeUtil.getLong(message, offset));
                    break;
                case 6: // FIXED32:
                    hashCode = (hashCode * 53) + (UnsafeUtil.getInt(message, offset));
                    break;
                case 7: // BOOL:
                    hashCode = (hashCode * 53) + Internal.hashBoolean(UnsafeUtil.getBoolean(message, offset));
                    break;
                case 8: // STRING:
                    hashCode = (hashCode * 53) + ((String) UnsafeUtil.getObject(message, offset)).hashCode();
                    break;
                case 9: // MESSAGE:
                {
                    int protoHash = 37;
                    Object submessage = UnsafeUtil.getObject(message, offset);
                    if (submessage != null) {
                        protoHash = submessage.hashCode();
                    }
                    hashCode = (53 * hashCode) + protoHash;
                    break;
                }
                case 10: // BYTES:
                    hashCode = (hashCode * 53) + UnsafeUtil.getObject(message, offset).hashCode();
                    break;
                case 11: // UINT32:
                    hashCode = (hashCode * 53) + (UnsafeUtil.getInt(message, offset));
                    break;
                case 12: // ENUM:
                    hashCode = (hashCode * 53) + (UnsafeUtil.getInt(message, offset));
                    break;
                case 13: // SFIXED32:
                    hashCode = (hashCode * 53) + (UnsafeUtil.getInt(message, offset));
                    break;
                case 14: // SFIXED64:
                    hashCode = (hashCode * 53) + Internal.hashLong(UnsafeUtil.getLong(message, offset));
                    break;
                case 15: // SINT32:
                    hashCode = (hashCode * 53) + (UnsafeUtil.getInt(message, offset));
                    break;
                case 16: // SINT64:
                    hashCode = (hashCode * 53) + Internal.hashLong(UnsafeUtil.getLong(message, offset));
                    break;

                case 17: // GROUP:
                {
                    int protoHash = 37;
                    Object submessage = UnsafeUtil.getObject(message, offset);
                    if (submessage != null) {
                        protoHash = submessage.hashCode();
                    }
                    hashCode = (53 * hashCode) + protoHash;
                    break;
                }
                case 18: // DOUBLE_LIST:
                case 19: // FLOAT_LIST:
                case 20: // INT64_LIST:
                case 21: // UINT64_LIST:
                case 22: // INT32_LIST:
                case 23: // FIXED64_LIST:
                case 24: // FIXED32_LIST:
                case 25: // BOOL_LIST:
                case 26: // STRING_LIST:
                case 27: // MESSAGE_LIST:
                case 28: // BYTES_LIST:
                case 29: // UINT32_LIST:
                case 30: // ENUM_LIST:
                case 31: // SFIXED32_LIST:
                case 32: // SFIXED64_LIST:
                case 33: // SINT32_LIST:
                case 34: // SINT64_LIST:
                case 35: // DOUBLE_LIST_PACKED:
                case 36: // FLOAT_LIST_PACKED:
                case 37: // INT64_LIST_PACKED:
                case 38: // UINT64_LIST_PACKED:
                case 39: // INT32_LIST_PACKED:
                case 40: // FIXED64_LIST_PACKED:
                case 41: // FIXED32_LIST_PACKED:
                case 42: // BOOL_LIST_PACKED:
                case 43: // UINT32_LIST_PACKED:
                case 44: // ENUM_LIST_PACKED:
                case 45: // SFIXED32_LIST_PACKED:
                case 46: // SFIXED64_LIST_PACKED:
                case 47: // SINT32_LIST_PACKED:
                case 48: // SINT64_LIST_PACKED:
                case 49: // GROUP_LIST:
                    hashCode = (hashCode * 53) + UnsafeUtil.getObject(message, offset).hashCode();
                    break;
                case 50: // MAP:
                    hashCode = (hashCode * 53) + UnsafeUtil.getObject(message, offset).hashCode();
                    break;
                case 51: // ONEOF_DOUBLE:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode =
                                (hashCode * 53)
                                        + Internal.hashLong(Double.doubleToLongBits(oneofDoubleAt(message, offset)));
                    }
                    break;
                case 52: // ONEOF_FLOAT:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + Float.floatToIntBits(oneofFloatAt(message, offset));
                    }
                    break;
                case 53: // ONEOF_INT64:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + Internal.hashLong(oneofLongAt(message, offset));
                    }
                    break;
                case 54: // ONEOF_UINT64:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + Internal.hashLong(oneofLongAt(message, offset));
                    }
                    break;
                case 55: // ONEOF_INT32:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + (oneofIntAt(message, offset));
                    }
                    break;
                case 56: // ONEOF_FIXED64:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + Internal.hashLong(oneofLongAt(message, offset));
                    }
                    break;
                case 57: // ONEOF_FIXED32:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + (oneofIntAt(message, offset));
                    }
                    break;
                case 58: // ONEOF_BOOL:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + Internal.hashBoolean(oneofBooleanAt(message, offset));
                    }
                    break;
                case 59: // ONEOF_STRING:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode =
                                (hashCode * 53) + ((String) UnsafeUtil.getObject(message, offset)).hashCode();
                    }
                    break;
                case 60: // ONEOF_MESSAGE:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        Object submessage = UnsafeUtil.getObject(message, offset);
                        hashCode = (53 * hashCode) + submessage.hashCode();
                    }
                    break;
                case 61: // ONEOF_BYTES:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + UnsafeUtil.getObject(message, offset).hashCode();
                    }
                    break;
                case 62: // ONEOF_UINT32:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + (oneofIntAt(message, offset));
                    }
                    break;
                case 63: // ONEOF_ENUM:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + (oneofIntAt(message, offset));
                    }
                    break;
                case 64: // ONEOF_SFIXED32:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + (oneofIntAt(message, offset));
                    }
                    break;
                case 65: // ONEOF_SFIXED64:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + Internal.hashLong(oneofLongAt(message, offset));
                    }
                    break;
                case 66: // ONEOF_SINT32:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + (oneofIntAt(message, offset));
                    }
                    break;
                case 67: // ONEOF_SINT64:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        hashCode = (hashCode * 53) + Internal.hashLong(oneofLongAt(message, offset));
                    }
                    break;
                case 68: // ONEOF_GROUP:
                    if (isOneofPresent(message, entryNumber, pos)) {
                        Object submessage = UnsafeUtil.getObject(message, offset);
                        hashCode = (53 * hashCode) + submessage.hashCode();
                    }
                    break;
                default:
                    // Assume it's an empty entry - just go to the next entry.
                    break;
            }
        }

        hashCode = (hashCode * 53) + unknownFieldSchema.getFromMessage(message).hashCode();

        if (hasExtensions) {
            hashCode = (hashCode * 53) + extensionSchema.getExtensions(message).hashCode();
        }

        return hashCode;
    }

    @Override
    public void mergeFrom(T message, T other) {
        if (other == null) {
            throw new NullPointerException();
        }
        for (int i = 0; i < buffer.length; i += INTS_PER_FIELD) {
            // A separate method allows for better JIT optimizations
            mergeSingleField(message, other, i);
        }

        SchemaUtil.mergeUnknownFields(unknownFieldSchema, message, other);

        if (hasExtensions) {
            SchemaUtil.mergeExtensions(extensionSchema, message, other);
        }
    }

    private void mergeSingleField(T message, T other, int pos) {
        final int typeAndOffset = typeAndOffsetAt(pos);
        final long offset = offset(typeAndOffset);
        final int number = numberAt(pos);

        switch (type(typeAndOffset)) {
            case 0: // DOUBLE:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putDouble(message, offset, UnsafeUtil.getDouble(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 1: // FLOAT:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putFloat(message, offset, UnsafeUtil.getFloat(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 2: // INT64:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putLong(message, offset, UnsafeUtil.getLong(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 3: // UINT64:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putLong(message, offset, UnsafeUtil.getLong(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 4: // INT32:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 5: // FIXED64:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putLong(message, offset, UnsafeUtil.getLong(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 6: // FIXED32:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 7: // BOOL:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putBoolean(message, offset, UnsafeUtil.getBoolean(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 8: // STRING:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putObject(message, offset, UnsafeUtil.getObject(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 9: // MESSAGE:
                mergeMessage(message, other, pos);
                break;
            case 10: // BYTES:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putObject(message, offset, UnsafeUtil.getObject(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 11: // UINT32:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 12: // ENUM:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 13: // SFIXED32:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 14: // SFIXED64:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putLong(message, offset, UnsafeUtil.getLong(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 15: // SINT32:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 16: // SINT64:
                if (isFieldPresent(other, pos)) {
                    UnsafeUtil.putLong(message, offset, UnsafeUtil.getLong(other, offset));
                    setFieldPresent(message, pos);
                }
                break;
            case 17: // GROUP:
                mergeMessage(message, other, pos);
                break;
            case 18: // DOUBLE_LIST:
            case 19: // FLOAT_LIST:
            case 20: // INT64_LIST:
            case 21: // UINT64_LIST:
            case 22: // INT32_LIST:
            case 23: // FIXED64_LIST:
            case 24: // FIXED32_LIST:
            case 25: // BOOL_LIST:
            case 26: // STRING_LIST:
            case 27: // MESSAGE_LIST:
            case 28: // BYTES_LIST:
            case 29: // UINT32_LIST:
            case 30: // ENUM_LIST:
            case 31: // SFIXED32_LIST:
            case 32: // SFIXED64_LIST:
            case 33: // SINT32_LIST:
            case 34: // SINT64_LIST:
            case 35: // DOUBLE_LIST_PACKED:
            case 36: // FLOAT_LIST_PACKED:
            case 37: // INT64_LIST_PACKED:
            case 38: // UINT64_LIST_PACKED:
            case 39: // INT32_LIST_PACKED:
            case 40: // FIXED64_LIST_PACKED:
            case 41: // FIXED32_LIST_PACKED:
            case 42: // BOOL_LIST_PACKED:
            case 43: // UINT32_LIST_PACKED:
            case 44: // ENUM_LIST_PACKED:
            case 45: // SFIXED32_LIST_PACKED:
            case 46: // SFIXED64_LIST_PACKED:
            case 47: // SINT32_LIST_PACKED:
            case 48: // SINT64_LIST_PACKED:
            case 49: // GROUP_LIST:
                listFieldSchema.mergeListsAt(message, other, offset);
                break;
            case 50: // MAP:
                SchemaUtil.mergeMap(mapFieldSchema, message, other, offset);
                break;
            case 51: // ONEOF_DOUBLE:
            case 52: // ONEOF_FLOAT:
            case 53: // ONEOF_INT64:
            case 54: // ONEOF_UINT64:
            case 55: // ONEOF_INT32:
            case 56: // ONEOF_FIXED64:
            case 57: // ONEOF_FIXED32:
            case 58: // ONEOF_BOOL:
            case 59: // ONEOF_STRING:
                if (isOneofPresent(other, number, pos)) {
                    UnsafeUtil.putObject(message, offset, UnsafeUtil.getObject(other, offset));
                    setOneofPresent(message, number, pos);
                }
                break;

            case 60: // ONEOF_MESSAGE:
                mergeOneofMessage(message, other, pos);
                break;
            case 61: // ONEOF_BYTES:
            case 62: // ONEOF_UINT32:
            case 63: // ONEOF_ENUM:
            case 64: // ONEOF_SFIXED32:
            case 65: // ONEOF_SFIXED64:
            case 66: // ONEOF_SINT32:
            case 67: // ONEOF_SINT64:
                if (isOneofPresent(other, number, pos)) {
                    UnsafeUtil.putObject(message, offset, UnsafeUtil.getObject(other, offset));
                    setOneofPresent(message, number, pos);
                }
                break;
            case 68: // ONEOF_GROUP:
                mergeOneofMessage(message, other, pos);
                break;
            default:
                break;
        }
    }

    private void mergeMessage(T message, T other, int pos) {
        final int typeAndOffset = typeAndOffsetAt(pos);
        final long offset = offset(typeAndOffset);

        if (!isFieldPresent(other, pos)) {
            return;
        }

        Object mine = UnsafeUtil.getObject(message, offset);
        Object theirs = UnsafeUtil.getObject(other, offset);
        if (mine != null && theirs != null) {
            Object merged = Internal.mergeMessage(mine, theirs);
            UnsafeUtil.putObject(message, offset, merged);
            setFieldPresent(message, pos);
        } else if (theirs != null) {
            UnsafeUtil.putObject(message, offset, theirs);
            setFieldPresent(message, pos);
        }
    }

    private void mergeOneofMessage(T message, T other, int pos) {
        int typeAndOffset = typeAndOffsetAt(pos);
        int number = numberAt(pos);
        long offset = offset(typeAndOffset);

        if (!isOneofPresent(other, number, pos)) {
            return;
        }
        Object mine = null;
        if (isOneofPresent(message, number, pos)) {
            mine = UnsafeUtil.getObject(message, offset);
        }
        Object theirs = UnsafeUtil.getObject(other, offset);
        if (mine != null && theirs != null) {
            Object merged = Internal.mergeMessage(mine, theirs);
            UnsafeUtil.putObject(message, offset, merged);
            setOneofPresent(message, number, pos);
        } else if (theirs != null) {
            UnsafeUtil.putObject(message, offset, theirs);
            setOneofPresent(message, number, pos);
        }
    }

    @Override
    public int getSerializedSize(T message) {
        return proto3 ? getSerializedSizeProto3(message) : getSerializedSizeProto2(message);
    }

    @SuppressWarnings("unchecked")
    private int getSerializedSizeProto2(T message) {
        int size = 0;

        final sun.misc.Unsafe unsafe = UNSAFE;
        int currentPresenceFieldOffset = NO_PRESENCE_SENTINEL;
        int currentPresenceField = 0;
        for (int i = 0; i < buffer.length; i += INTS_PER_FIELD) {
            final int typeAndOffset = typeAndOffsetAt(i);
            final int number = numberAt(i);

            int fieldType = type(typeAndOffset);
            int presenceMaskAndOffset = 0;
            int presenceMask = 0;
            if (fieldType <= 17) {
                presenceMaskAndOffset = buffer[i + 2];
                final int presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
                presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
                if (presenceFieldOffset != currentPresenceFieldOffset) {
                    currentPresenceFieldOffset = presenceFieldOffset;
                    currentPresenceField = unsafe.getInt(message, (long) presenceFieldOffset);
                }
            } else if (useCachedSizeField
                    && fieldType >= FieldType.DOUBLE_LIST_PACKED.id()
                    && fieldType <= FieldType.SINT64_LIST_PACKED.id()) {
                presenceMaskAndOffset = buffer[i + 2] & OFFSET_MASK;
            }

            final long offset = offset(typeAndOffset);

            switch (fieldType) {
                case 0: // DOUBLE:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeDoubleSize(number, 0);
                    }
                    break;
                case 1: // FLOAT:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeFloatSize(number, 0);
                    }
                    break;
                case 2: // INT64:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeInt64Size(number, unsafe.getLong(message, offset));
                    }
                    break;
                case 3: // UINT64:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeUInt64Size(number, unsafe.getLong(message, offset));
                    }
                    break;
                case 4: // INT32:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeInt32Size(number, unsafe.getInt(message, offset));
                    }
                    break;
                case 5: // FIXED64:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeFixed64Size(number, 0);
                    }
                    break;
                case 6: // FIXED32:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeFixed32Size(number, 0);
                    }
                    break;
                case 7: // BOOL:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeBoolSize(number, true);
                    }
                    break;
                case 8: // STRING:
                    if ((currentPresenceField & presenceMask) != 0) {
                        Object value = unsafe.getObject(message, offset);
                        if (value instanceof ByteString) {
                            size += CodedOutputStream.computeBytesSize(number, (ByteString) value);
                        } else {
                            size += CodedOutputStream.computeStringSize(number, (String) value);
                        }
                    }
                    break;
                case 9: // MESSAGE:
                    if ((currentPresenceField & presenceMask) != 0) {
                        Object value = unsafe.getObject(message, offset);
                        size += SchemaUtil.computeSizeMessage(number, value, getMessageFieldSchema(i));
                    }
                    break;
                case 10: // BYTES:
                    if ((currentPresenceField & presenceMask) != 0) {
                        ByteString value = (ByteString) unsafe.getObject(message, offset);
                        size += CodedOutputStream.computeBytesSize(number, value);
                    }
                    break;
                case 11: // UINT32:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeUInt32Size(number, unsafe.getInt(message, offset));
                    }
                    break;
                case 12: // ENUM:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeEnumSize(number, unsafe.getInt(message, offset));
                    }
                    break;
                case 13: // SFIXED32:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeSFixed32Size(number, 0);
                    }
                    break;
                case 14: // SFIXED64:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeSFixed64Size(number, 0);
                    }
                    break;
                case 15: // SINT32:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeSInt32Size(number, unsafe.getInt(message, offset));
                    }
                    break;
                case 16: // SINT64:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size += CodedOutputStream.computeSInt64Size(number, unsafe.getLong(message, offset));
                    }
                    break;
                case 17: // GROUP:
                    if ((currentPresenceField & presenceMask) != 0) {
                        size +=
                                CodedOutputStream.computeGroupSize(
                                        number,
                                        (MessageLite) unsafe.getObject(message, offset),
                                        getMessageFieldSchema(i));
                    }
                    break;
                case 18: // DOUBLE_LIST:
                    size +=
                            SchemaUtil.computeSizeFixed64List(
                                    number, (List<?>) unsafe.getObject(message, offset), false);
                    break;
                case 19: // FLOAT_LIST:
                    size +=
                            SchemaUtil.computeSizeFixed32List(
                                    number, (List<?>) unsafe.getObject(message, offset), false);
                    break;
                case 20: // INT64_LIST:
                    size +=
                            SchemaUtil.computeSizeInt64List(
                                    number, (List<Long>) unsafe.getObject(message, offset), false);
                    break;
                case 21: // UINT64_LIST:
                    size +=
                            SchemaUtil.computeSizeUInt64List(
                                    number, (List<Long>) unsafe.getObject(message, offset), false);
                    break;
                case 22: // INT32_LIST:
                    size +=
                            SchemaUtil.computeSizeInt32List(
                                    number, (List<Integer>) unsafe.getObject(message, offset), false);
                    break;
                case 23: // FIXED64_LIST:
                    size +=
                            SchemaUtil.computeSizeFixed64List(
                                    number, (List<?>) unsafe.getObject(message, offset), false);
                    break;
                case 24: // FIXED32_LIST:
                    size +=
                            SchemaUtil.computeSizeFixed32List(
                                    number, (List<?>) unsafe.getObject(message, offset), false);
                    break;
                case 25: // BOOL_LIST:
                    size +=
                            SchemaUtil.computeSizeBoolList(
                                    number, (List<?>) unsafe.getObject(message, offset), false);
                    break;
                case 26: // STRING_LIST:
                    size +=
                            SchemaUtil.computeSizeStringList(number, (List<?>) unsafe.getObject(message, offset));
                    break;
                case 27: // MESSAGE_LIST:
                    size +=
                            SchemaUtil.computeSizeMessageList(
                                    number, (List<?>) unsafe.getObject(message, offset), getMessageFieldSchema(i));
                    break;
                case 28: // BYTES_LIST:
                    size +=
                            SchemaUtil.computeSizeByteStringList(
                                    number, (List<ByteString>) unsafe.getObject(message, offset));
                    break;
                case 29: // UINT32_LIST:
                    size +=
                            SchemaUtil.computeSizeUInt32List(
                                    number, (List<Integer>) unsafe.getObject(message, offset), false);
                    break;
                case 30: // ENUM_LIST:
                    size +=
                            SchemaUtil.computeSizeEnumList(
                                    number, (List<Integer>) unsafe.getObject(message, offset), false);
                    break;
                case 31: // SFIXED32_LIST:
                    size +=
                            SchemaUtil.computeSizeFixed32List(
                                    number, (List<Integer>) unsafe.getObject(message, offset), false);
                    break;
                case 32: // SFIXED64_LIST:
                    size +=
                            SchemaUtil.computeSizeFixed64List(
                                    number, (List<Long>) unsafe.getObject(message, offset), false);
                    break;
                case 33: // SINT32_LIST:
                    size +=
                            SchemaUtil.computeSizeSInt32List(
                                    number, (List<Integer>) unsafe.getObject(message, offset), false);
                    break;
                case 34: // SINT64_LIST:
                    size +=
                            SchemaUtil.computeSizeSInt64List(
                                    number, (List<Long>) unsafe.getObject(message, offset), false);
                    break;
                case 35: { // DOUBLE_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeFixed64ListNoTag(
                                    (List<Double>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 36: { // FLOAT_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeFixed32ListNoTag(
                                    (List<Float>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 37: { // INT64_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeInt64ListNoTag(
                                    (List<Long>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 38: { // UINT64_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeUInt64ListNoTag(
                                    (List<Long>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 39: { // INT32_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeInt32ListNoTag(
                                    (List<Integer>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 40: { // FIXED64_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeFixed64ListNoTag(
                                    (List<Long>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 41: { // FIXED32_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeFixed32ListNoTag(
                                    (List<Integer>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 42: { // BOOL_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeBoolListNoTag(
                                    (List<Boolean>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 43: { // UINT32_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeUInt32ListNoTag(
                                    (List<Integer>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 44: { // ENUM_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeEnumListNoTag(
                                    (List<Integer>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 45: { // SFIXED32_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeFixed32ListNoTag(
                                    (List<Integer>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 46: { // SFIXED64_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeFixed64ListNoTag(
                                    (List<Long>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 47: { // SINT32_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeSInt32ListNoTag(
                                    (List<Integer>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 48: { // SINT64_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeSInt64ListNoTag(
                                    (List<Long>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) presenceMaskAndOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 49: // GROUP_LIST:
                    size +=
                            SchemaUtil.computeSizeGroupList(
                                    number,
                                    (List<MessageLite>) unsafe.getObject(message, offset),
                                    getMessageFieldSchema(i));
                    break;
                case 50: // MAP:
                    // TODO(dweis): Use schema cache.
                    size +=
                            mapFieldSchema.getSerializedSize(
                                    number, unsafe.getObject(message, offset), getMapFieldDefaultEntry(i));
                    break;
                case 51: // ONEOF_DOUBLE:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeDoubleSize(number, 0);
                    }
                    break;
                case 52: // ONEOF_FLOAT:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeFloatSize(number, 0);
                    }
                    break;
                case 53: // ONEOF_INT64:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeInt64Size(number, oneofLongAt(message, offset));
                    }
                    break;
                case 54: // ONEOF_UINT64:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeUInt64Size(number, oneofLongAt(message, offset));
                    }
                    break;
                case 55: // ONEOF_INT32:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeInt32Size(number, oneofIntAt(message, offset));
                    }
                    break;
                case 56: // ONEOF_FIXED64:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeFixed64Size(number, 0);
                    }
                    break;
                case 57: // ONEOF_FIXED32:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeFixed32Size(number, 0);
                    }
                    break;
                case 58: // ONEOF_BOOL:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeBoolSize(number, true);
                    }
                    break;
                case 59: // ONEOF_STRING:
                    if (isOneofPresent(message, number, i)) {
                        Object value = unsafe.getObject(message, offset);
                        if (value instanceof ByteString) {
                            size += CodedOutputStream.computeBytesSize(number, (ByteString) value);
                        } else {
                            size += CodedOutputStream.computeStringSize(number, (String) value);
                        }
                    }
                    break;
                case 60: // ONEOF_MESSAGE:
                    if (isOneofPresent(message, number, i)) {
                        Object value = unsafe.getObject(message, offset);
                        size += SchemaUtil.computeSizeMessage(number, value, getMessageFieldSchema(i));
                    }
                    break;
                case 61: // ONEOF_BYTES:
                    if (isOneofPresent(message, number, i)) {
                        size +=
                                CodedOutputStream.computeBytesSize(
                                        number, (ByteString) unsafe.getObject(message, offset));
                    }
                    break;
                case 62: // ONEOF_UINT32:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeUInt32Size(number, oneofIntAt(message, offset));
                    }
                    break;
                case 63: // ONEOF_ENUM:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeEnumSize(number, oneofIntAt(message, offset));
                    }
                    break;
                case 64: // ONEOF_SFIXED32:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeSFixed32Size(number, 0);
                    }
                    break;
                case 65: // ONEOF_SFIXED64:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeSFixed64Size(number, 0);
                    }
                    break;
                case 66: // ONEOF_SINT32:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeSInt32Size(number, oneofIntAt(message, offset));
                    }
                    break;
                case 67: // ONEOF_SINT64:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeSInt64Size(number, oneofLongAt(message, offset));
                    }
                    break;
                case 68: // ONEOF_GROUP:
                    if (isOneofPresent(message, number, i)) {
                        size +=
                                CodedOutputStream.computeGroupSize(
                                        number,
                                        (MessageLite) unsafe.getObject(message, offset),
                                        getMessageFieldSchema(i));
                    }
                    break;
                default:
                    // Assume it's an empty entry.
            }
        }

        size += getUnknownFieldsSerializedSize(unknownFieldSchema, message);

        if (hasExtensions) {
            size += extensionSchema.getExtensions(message).getSerializedSize();
        }

        return size;
    }

    private int getSerializedSizeProto3(T message) {
        final sun.misc.Unsafe unsafe = UNSAFE;
        int size = 0;
        for (int i = 0; i < buffer.length; i += INTS_PER_FIELD) {
            final int typeAndOffset = typeAndOffsetAt(i);
            final int fieldType = type(typeAndOffset);
            final int number = numberAt(i);

            final long offset = offset(typeAndOffset);
            final int cachedSizeOffset =
                    fieldType >= FieldType.DOUBLE_LIST_PACKED.id()
                            && fieldType <= FieldType.SINT64_LIST_PACKED.id()
                            ? buffer[i + 2] & OFFSET_MASK
                            : 0;

            switch (fieldType) {
                case 0: // DOUBLE:
                    if (isFieldPresent(message, i)) {
                        size += CodedOutputStream.computeDoubleSize(number, 0);
                    }
                    break;
                case 1: // FLOAT:
                    if (isFieldPresent(message, i)) {
                        size += CodedOutputStream.computeFloatSize(number, 0);
                    }
                    break;
                case 2: // INT64:
                    if (isFieldPresent(message, i)) {
                        size += CodedOutputStream.computeInt64Size(number, UnsafeUtil.getLong(message, offset));
                    }
                    break;
                case 3: // UINT64:
                    if (isFieldPresent(message, i)) {
                        size +=
                                CodedOutputStream.computeUInt64Size(number, UnsafeUtil.getLong(message, offset));
                    }
                    break;
                case 4: // INT32:
                    if (isFieldPresent(message, i)) {
                        size += CodedOutputStream.computeInt32Size(number, UnsafeUtil.getInt(message, offset));
                    }
                    break;
                case 5: // FIXED64:
                    if (isFieldPresent(message, i)) {
                        size += CodedOutputStream.computeFixed64Size(number, 0);
                    }
                    break;
                case 6: // FIXED32:
                    if (isFieldPresent(message, i)) {
                        size += CodedOutputStream.computeFixed32Size(number, 0);
                    }
                    break;
                case 7: // BOOL:
                    if (isFieldPresent(message, i)) {
                        size += CodedOutputStream.computeBoolSize(number, true);
                    }
                    break;
                case 8: // STRING:
                    if (isFieldPresent(message, i)) {
                        Object value = UnsafeUtil.getObject(message, offset);
                        if (value instanceof ByteString) {
                            size += CodedOutputStream.computeBytesSize(number, (ByteString) value);
                        } else {
                            size += CodedOutputStream.computeStringSize(number, (String) value);
                        }
                    }
                    break;
                case 9: // MESSAGE:
                    if (isFieldPresent(message, i)) {
                        Object value = UnsafeUtil.getObject(message, offset);
                        size += SchemaUtil.computeSizeMessage(number, value, getMessageFieldSchema(i));
                    }
                    break;
                case 10: // BYTES:
                    if (isFieldPresent(message, i)) {
                        ByteString value = (ByteString) UnsafeUtil.getObject(message, offset);
                        size += CodedOutputStream.computeBytesSize(number, value);
                    }
                    break;
                case 11: // UINT32:
                    if (isFieldPresent(message, i)) {
                        size += CodedOutputStream.computeUInt32Size(number, UnsafeUtil.getInt(message, offset));
                    }
                    break;
                case 12: // ENUM:
                    if (isFieldPresent(message, i)) {
                        size += CodedOutputStream.computeEnumSize(number, UnsafeUtil.getInt(message, offset));
                    }
                    break;
                case 13: // SFIXED32:
                    if (isFieldPresent(message, i)) {
                        size += CodedOutputStream.computeSFixed32Size(number, 0);
                    }
                    break;
                case 14: // SFIXED64:
                    if (isFieldPresent(message, i)) {
                        size += CodedOutputStream.computeSFixed64Size(number, 0);
                    }
                    break;
                case 15: // SINT32:
                    if (isFieldPresent(message, i)) {
                        size += CodedOutputStream.computeSInt32Size(number, UnsafeUtil.getInt(message, offset));
                    }
                    break;
                case 16: // SINT64:
                    if (isFieldPresent(message, i)) {
                        size +=
                                CodedOutputStream.computeSInt64Size(number, UnsafeUtil.getLong(message, offset));
                    }
                    break;
                case 17: // GROUP:
                    if (isFieldPresent(message, i)) {
                        size +=
                                CodedOutputStream.computeGroupSize(
                                        number,
                                        (MessageLite) UnsafeUtil.getObject(message, offset),
                                        getMessageFieldSchema(i));
                    }
                    break;
                case 18: // DOUBLE_LIST:
                    size += SchemaUtil.computeSizeFixed64List(number, listAt(message, offset), false);
                    break;
                case 19: // FLOAT_LIST:
                    size += SchemaUtil.computeSizeFixed32List(number, listAt(message, offset), false);
                    break;
                case 20: // INT64_LIST:
                    size +=
                            SchemaUtil.computeSizeInt64List(number, (List<Long>) listAt(message, offset), false);
                    break;
                case 21: // UINT64_LIST:
                    size +=
                            SchemaUtil.computeSizeUInt64List(number, (List<Long>) listAt(message, offset), false);
                    break;
                case 22: // INT32_LIST:
                    size +=
                            SchemaUtil.computeSizeInt32List(
                                    number, (List<Integer>) listAt(message, offset), false);
                    break;
                case 23: // FIXED64_LIST:
                    size += SchemaUtil.computeSizeFixed64List(number, listAt(message, offset), false);
                    break;
                case 24: // FIXED32_LIST:
                    size += SchemaUtil.computeSizeFixed32List(number, listAt(message, offset), false);
                    break;
                case 25: // BOOL_LIST:
                    size += SchemaUtil.computeSizeBoolList(number, listAt(message, offset), false);
                    break;
                case 26: // STRING_LIST:
                    size += SchemaUtil.computeSizeStringList(number, listAt(message, offset));
                    break;
                case 27: // MESSAGE_LIST:
                    size +=
                            SchemaUtil.computeSizeMessageList(
                                    number, listAt(message, offset), getMessageFieldSchema(i));
                    break;
                case 28: // BYTES_LIST:
                    size +=
                            SchemaUtil.computeSizeByteStringList(
                                    number, (List<ByteString>) listAt(message, offset));
                    break;
                case 29: // UINT32_LIST:
                    size +=
                            SchemaUtil.computeSizeUInt32List(
                                    number, (List<Integer>) listAt(message, offset), false);
                    break;
                case 30: // ENUM_LIST:
                    size +=
                            SchemaUtil.computeSizeEnumList(
                                    number, (List<Integer>) listAt(message, offset), false);
                    break;
                case 31: // SFIXED32_LIST:
                    size += SchemaUtil.computeSizeFixed32List(number, listAt(message, offset), false);
                    break;
                case 32: // SFIXED64_LIST:
                    size += SchemaUtil.computeSizeFixed64List(number, listAt(message, offset), false);
                    break;
                case 33: // SINT32_LIST:
                    size +=
                            SchemaUtil.computeSizeSInt32List(
                                    number, (List<Integer>) listAt(message, offset), false);
                    break;
                case 34: // SINT64_LIST:
                    size +=
                            SchemaUtil.computeSizeSInt64List(number, (List<Long>) listAt(message, offset), false);
                    break;
                case 35: { // DOUBLE_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeFixed64ListNoTag(
                                    (List<Double>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 36: { // FLOAT_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeFixed32ListNoTag(
                                    (List<Float>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 37: { // INT64_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeInt64ListNoTag(
                                    (List<Long>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 38: { // UINT64_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeUInt64ListNoTag(
                                    (List<Long>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 39: { // INT32_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeInt32ListNoTag(
                                    (List<Integer>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 40: { // FIXED64_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeFixed64ListNoTag(
                                    (List<Long>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 41: { // FIXED32_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeFixed32ListNoTag(
                                    (List<Integer>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 42: { // BOOL_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeBoolListNoTag(
                                    (List<Boolean>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 43: { // UINT32_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeUInt32ListNoTag(
                                    (List<Integer>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 44: { // ENUM_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeEnumListNoTag(
                                    (List<Integer>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 45: { // SFIXED32_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeFixed32ListNoTag(
                                    (List<Integer>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 46: { // SFIXED64_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeFixed64ListNoTag(
                                    (List<Long>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 47: { // SINT32_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeSInt32ListNoTag(
                                    (List<Integer>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 48: { // SINT64_LIST_PACKED:
                    int fieldSize =
                            SchemaUtil.computeSizeSInt64ListNoTag(
                                    (List<Long>) unsafe.getObject(message, offset));
                    if (fieldSize > 0) {
                        if (useCachedSizeField) {
                            unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
                        }
                        size +=
                                CodedOutputStream.computeTagSize(number)
                                        + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                                        + fieldSize;
                    }
                    break;
                }
                case 49: // GROUP_LIST:
                    size +=
                            SchemaUtil.computeSizeGroupList(
                                    number, (List<MessageLite>) listAt(message, offset), getMessageFieldSchema(i));
                    break;
                case 50: // MAP:
                    // TODO(dweis): Use schema cache.
                    size +=
                            mapFieldSchema.getSerializedSize(
                                    number, UnsafeUtil.getObject(message, offset), getMapFieldDefaultEntry(i));
                    break;
                case 51: // ONEOF_DOUBLE:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeDoubleSize(number, 0);
                    }
                    break;
                case 52: // ONEOF_FLOAT:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeFloatSize(number, 0);
                    }
                    break;
                case 53: // ONEOF_INT64:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeInt64Size(number, oneofLongAt(message, offset));
                    }
                    break;
                case 54: // ONEOF_UINT64:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeUInt64Size(number, oneofLongAt(message, offset));
                    }
                    break;
                case 55: // ONEOF_INT32:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeInt32Size(number, oneofIntAt(message, offset));
                    }
                    break;
                case 56: // ONEOF_FIXED64:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeFixed64Size(number, 0);
                    }
                    break;
                case 57: // ONEOF_FIXED32:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeFixed32Size(number, 0);
                    }
                    break;
                case 58: // ONEOF_BOOL:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeBoolSize(number, true);
                    }
                    break;
                case 59: // ONEOF_STRING:
                    if (isOneofPresent(message, number, i)) {
                        Object value = UnsafeUtil.getObject(message, offset);
                        if (value instanceof ByteString) {
                            size += CodedOutputStream.computeBytesSize(number, (ByteString) value);
                        } else {
                            size += CodedOutputStream.computeStringSize(number, (String) value);
                        }
                    }
                    break;
                case 60: // ONEOF_MESSAGE:
                    if (isOneofPresent(message, number, i)) {
                        Object value = UnsafeUtil.getObject(message, offset);
                        size += SchemaUtil.computeSizeMessage(number, value, getMessageFieldSchema(i));
                    }
                    break;
                case 61: // ONEOF_BYTES:
                    if (isOneofPresent(message, number, i)) {
                        size +=
                                CodedOutputStream.computeBytesSize(
                                        number, (ByteString) UnsafeUtil.getObject(message, offset));
                    }
                    break;
                case 62: // ONEOF_UINT32:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeUInt32Size(number, oneofIntAt(message, offset));
                    }
                    break;
                case 63: // ONEOF_ENUM:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeEnumSize(number, oneofIntAt(message, offset));
                    }
                    break;
                case 64: // ONEOF_SFIXED32:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeSFixed32Size(number, 0);
                    }
                    break;
                case 65: // ONEOF_SFIXED64:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeSFixed64Size(number, 0);
                    }
                    break;
                case 66: // ONEOF_SINT32:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeSInt32Size(number, oneofIntAt(message, offset));
                    }
                    break;
                case 67: // ONEOF_SINT64:
                    if (isOneofPresent(message, number, i)) {
                        size += CodedOutputStream.computeSInt64Size(number, oneofLongAt(message, offset));
                    }
                    break;
                case 68: // ONEOF_GROUP:
                    if (isOneofPresent(message, number, i)) {
                        size +=
                                CodedOutputStream.computeGroupSize(
                                        number,
                                        (MessageLite) UnsafeUtil.getObject(message, offset),
                                        getMessageFieldSchema(i));
                    }
                    break;
                default:
                    // Assume it's an empty entry.
            }
        }

        size += getUnknownFieldsSerializedSize(unknownFieldSchema, message);

        return size;
    }

    private <UT, UB> int getUnknownFieldsSerializedSize(
            UnknownFieldSchema<UT, UB> schema, T message) {
        UT unknowns = schema.getFromMessage(message);
        return schema.getSerializedSize(unknowns);
    }

    private static List<?> listAt(Object message, long offset) {
        return (List<?>) UnsafeUtil.getObject(message, offset);
    }

    @SuppressWarnings("unchecked")
    @Override
    // TODO(nathanmittler): Consider serializing oneof fields last so that only one entry per
    // oneof is actually serialized. This would mean that we would violate the serialization order
    // contract. It should also be noted that Go currently does this.
    public void writeTo(T message, Writer writer) throws IOException {
        if (writer.fieldOrder() == Writer.FieldOrder.DESCENDING) {
            writeFieldsInDescendingOrder(message, writer);
        } else {
            if (proto3) {
                writeFieldsInAscendingOrderProto3(message, writer);
            } else {
                writeFieldsInAscendingOrderProto2(message, writer);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void writeFieldsInAscendingOrderProto2(T message, Writer writer) throws IOException {
        Iterator<? extends Map.Entry<?, ?>> extensionIterator = null;
        Map.Entry nextExtension = null;
        if (hasExtensions) {
            FieldSet<?> extensions = extensionSchema.getExtensions(message);
            if (!extensions.isEmpty()) {
                extensionIterator = extensions.iterator();
                nextExtension = extensionIterator.next();
            }
        }
        int currentPresenceFieldOffset = NO_PRESENCE_SENTINEL;
        int currentPresenceField = 0;
        final int bufferLength = buffer.length;
        final sun.misc.Unsafe unsafe = UNSAFE;
        for (int pos = 0; pos < bufferLength; pos += INTS_PER_FIELD) {
            final int typeAndOffset = typeAndOffsetAt(pos);
            final int number = numberAt(pos);
            final int fieldType = type(typeAndOffset);

            int presenceMaskAndOffset = 0;
            int presenceMask = 0;
            if (fieldType <= 17) {
                presenceMaskAndOffset = buffer[pos + 2];
                final int presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
                if (presenceFieldOffset != currentPresenceFieldOffset) {
                    currentPresenceFieldOffset = presenceFieldOffset;
                    currentPresenceField = unsafe.getInt(message, (long) presenceFieldOffset);
                }
                presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
            }

            // Write any extensions that need to be written before the current field.
            while (nextExtension != null && extensionSchema.extensionNumber(nextExtension) <= number) {
                extensionSchema.serializeExtension(writer, nextExtension);
                nextExtension = extensionIterator.hasNext() ? extensionIterator.next() : null;
            }
            final long offset = offset(typeAndOffset);

            switch (fieldType) {
                case 0: // DOUBLE:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeDouble(number, doubleAt(message, offset));
                    }
                    break;
                case 1: // FLOAT:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeFloat(number, floatAt(message, offset));
                    }
                    break;
                case 2: // INT64:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeInt64(number, unsafe.getLong(message, offset));
                    }
                    break;
                case 3: // UINT64:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeUInt64(number, unsafe.getLong(message, offset));
                    }
                    break;
                case 4: // INT32:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeInt32(number, unsafe.getInt(message, offset));
                    }
                    break;
                case 5: // FIXED64:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeFixed64(number, unsafe.getLong(message, offset));
                    }
                    break;
                case 6: // FIXED32:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeFixed32(number, unsafe.getInt(message, offset));
                    }
                    break;
                case 7: // BOOL:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeBool(number, booleanAt(message, offset));
                    }
                    break;
                case 8: // STRING:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writeString(number, unsafe.getObject(message, offset), writer);
                    }
                    break;
                case 9: // MESSAGE:
                    if ((currentPresenceField & presenceMask) != 0) {
                        Object value = unsafe.getObject(message, offset);
                        writer.writeMessage(number, value, getMessageFieldSchema(pos));
                    }
                    break;
                case 10: // BYTES:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeBytes(number, (ByteString) unsafe.getObject(message, offset));
                    }
                    break;
                case 11: // UINT32:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeUInt32(number, unsafe.getInt(message, offset));
                    }
                    break;
                case 12: // ENUM:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeEnum(number, unsafe.getInt(message, offset));
                    }
                    break;
                case 13: // SFIXED32:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeSFixed32(number, unsafe.getInt(message, offset));
                    }
                    break;
                case 14: // SFIXED64:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeSFixed64(number, unsafe.getLong(message, offset));
                    }
                    break;
                case 15: // SINT32:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeSInt32(number, unsafe.getInt(message, offset));
                    }
                    break;
                case 16: // SINT64:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeSInt64(number, unsafe.getLong(message, offset));
                    }
                    break;
                case 17: // GROUP:
                    if ((currentPresenceField & presenceMask) != 0) {
                        writer.writeGroup(
                                number, unsafe.getObject(message, offset), getMessageFieldSchema(pos));
                    }
                    break;
                case 18: // DOUBLE_LIST:
                    SchemaUtil.writeDoubleList(
                            numberAt(pos), (List<Double>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 19: // FLOAT_LIST:
                    SchemaUtil.writeFloatList(
                            numberAt(pos), (List<Float>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 20: // INT64_LIST:
                    SchemaUtil.writeInt64List(
                            numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 21: // UINT64_LIST:
                    SchemaUtil.writeUInt64List(
                            numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 22: // INT32_LIST:
                    SchemaUtil.writeInt32List(
                            numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 23: // FIXED64_LIST:
                    SchemaUtil.writeFixed64List(
                            numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 24: // FIXED32_LIST:
                    SchemaUtil.writeFixed32List(
                            numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 25: // BOOL_LIST:
                    SchemaUtil.writeBoolList(
                            numberAt(pos), (List<Boolean>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 26: // STRING_LIST:
                    SchemaUtil.writeStringList(
                            numberAt(pos), (List<String>) unsafe.getObject(message, offset), writer);
                    break;
                case 27: // MESSAGE_LIST:
                    SchemaUtil.writeMessageList(
                            numberAt(pos),
                            (List<?>) unsafe.getObject(message, offset),
                            writer,
                            getMessageFieldSchema(pos));
                    break;
                case 28: // BYTES_LIST:
                    SchemaUtil.writeBytesList(
                            numberAt(pos), (List<ByteString>) unsafe.getObject(message, offset), writer);
                    break;
                case 29: // UINT32_LIST:
                    SchemaUtil.writeUInt32List(
                            numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 30: // ENUM_LIST:
                    SchemaUtil.writeEnumList(
                            numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 31: // SFIXED32_LIST:
                    SchemaUtil.writeSFixed32List(
                            numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 32: // SFIXED64_LIST:
                    SchemaUtil.writeSFixed64List(
                            numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 33: // SINT32_LIST:
                    SchemaUtil.writeSInt32List(
                            numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 34: // SINT64_LIST:
                    SchemaUtil.writeSInt64List(
                            numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, false);
                    break;
                case 35: // DOUBLE_LIST_PACKED:
                    // TODO(xiaofeng): Make use of cached field size to speed up serialization.
                    SchemaUtil.writeDoubleList(
                            numberAt(pos), (List<Double>) unsafe.getObject(message, offset), writer, true);
                    break;
                case 36: // FLOAT_LIST_PACKED:
                    SchemaUtil.writeFloatList(
                            numberAt(pos), (List<Float>) unsafe.getObject(message, offset), writer, true);
                    break;
                case 37: // INT64_LIST_PACKED:
                    SchemaUtil.writeInt64List(
                            numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, true);
                    break;
                case 38: // UINT64_LIST_PACKED:
                    SchemaUtil.writeUInt64List(
                            numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, true);
                    break;
                case 39: // INT32_LIST_PACKED:
                    SchemaUtil.writeInt32List(
                            numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, true);
                    break;
                case 40: // FIXED64_LIST_PACKED:
                    SchemaUtil.writeFixed64List(
                            numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, true);
                    break;
                case 41: // FIXED32_LIST_PACKED:
                    SchemaUtil.writeFixed32List(
                            numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, true);

                    break;
                case 42: // BOOL_LIST_PACKED:
                    SchemaUtil.writeBoolList(
                            numberAt(pos), (List<Boolean>) unsafe.getObject(message, offset), writer, true);
                    break;
                case 43: // UINT32_LIST_PACKED:
                    SchemaUtil.writeUInt32List(
                            numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, true);
                    break;
                case 44: // ENUM_LIST_PACKED:
                    SchemaUtil.writeEnumList(
                            numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, true);
                    break;
                case 45: // SFIXED32_LIST_PACKED:
                    SchemaUtil.writeSFixed32List(
                            numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, true);
                    break;
                case 46: // SFIXED64_LIST_PACKED:
                    SchemaUtil.writeSFixed64List(
                            numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, true);
                    break;
                case 47: // SINT32_LIST_PACKED:
                    SchemaUtil.writeSInt32List(
                            numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, true);
                    break;
                case 48: // SINT64_LIST_PACKED:
                    SchemaUtil.writeSInt64List(
                            numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, true);
                    break;
                case 49: // GROUP_LIST:
                    SchemaUtil.writeGroupList(
                            numberAt(pos),
                            (List<?>) unsafe.getObject(message, offset),
                            writer,
                            getMessageFieldSchema(pos));
                    break;
                case 50: // MAP:
                    // TODO(dweis): Use schema cache.
                    writeMapHelper(writer, number, unsafe.getObject(message, offset), pos);
                    break;
                case 51: // ONEOF_DOUBLE:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeDouble(number, oneofDoubleAt(message, offset));
                    }
                    break;
                case 52: // ONEOF_FLOAT:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeFloat(number, oneofFloatAt(message, offset));
                    }
                    break;
                case 53: // ONEOF_INT64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeInt64(number, oneofLongAt(message, offset));
                    }
                    break;
                case 54: // ONEOF_UINT64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeUInt64(number, oneofLongAt(message, offset));
                    }
                    break;
                case 55: // ONEOF_INT32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeInt32(number, oneofIntAt(message, offset));
                    }
                    break;
                case 56: // ONEOF_FIXED64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeFixed64(number, oneofLongAt(message, offset));
                    }
                    break;
                case 57: // ONEOF_FIXED32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeFixed32(number, oneofIntAt(message, offset));
                    }
                    break;
                case 58: // ONEOF_BOOL:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeBool(number, oneofBooleanAt(message, offset));
                    }
                    break;
                case 59: // ONEOF_STRING:
                    if (isOneofPresent(message, number, pos)) {
                        writeString(number, unsafe.getObject(message, offset), writer);
                    }
                    break;
                case 60: // ONEOF_MESSAGE:
                    if (isOneofPresent(message, number, pos)) {
                        Object value = unsafe.getObject(message, offset);
                        writer.writeMessage(number, value, getMessageFieldSchema(pos));
                    }
                    break;
                case 61: // ONEOF_BYTES:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeBytes(number, (ByteString) unsafe.getObject(message, offset));
                    }
                    break;
                case 62: // ONEOF_UINT32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeUInt32(number, oneofIntAt(message, offset));
                    }
                    break;
                case 63: // ONEOF_ENUM:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeEnum(number, oneofIntAt(message, offset));
                    }
                    break;
                case 64: // ONEOF_SFIXED32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeSFixed32(number, oneofIntAt(message, offset));
                    }
                    break;
                case 65: // ONEOF_SFIXED64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeSFixed64(number, oneofLongAt(message, offset));
                    }
                    break;
                case 66: // ONEOF_SINT32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeSInt32(number, oneofIntAt(message, offset));
                    }
                    break;
                case 67: // ONEOF_SINT64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeSInt64(number, oneofLongAt(message, offset));
                    }
                    break;
                case 68: // ONEOF_GROUP:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeGroup(
                                number, unsafe.getObject(message, offset), getMessageFieldSchema(pos));
                    }
                    break;
                default:
                    // Assume it's an empty entry - just go to the next entry.
                    break;
            }
        }
        while (nextExtension != null) {
            extensionSchema.serializeExtension(writer, nextExtension);
            nextExtension = extensionIterator.hasNext() ? extensionIterator.next() : null;
        }
        writeUnknownInMessageTo(unknownFieldSchema, message, writer);
    }

    @SuppressWarnings("unchecked")
    private void writeFieldsInAscendingOrderProto3(T message, Writer writer) throws IOException {
        Iterator<? extends Map.Entry<?, ?>> extensionIterator = null;
        Map.Entry nextExtension = null;
        if (hasExtensions) {
            FieldSet<?> extensions = extensionSchema.getExtensions(message);
            if (!extensions.isEmpty()) {
                extensionIterator = extensions.iterator();
                nextExtension = extensionIterator.next();
            }
        }
        final int bufferLength = buffer.length;
        for (int pos = 0; pos < bufferLength; pos += INTS_PER_FIELD) {
            final int typeAndOffset = typeAndOffsetAt(pos);
            final int number = numberAt(pos);

            // Write any extensions that need to be written before the current field.
            while (nextExtension != null && extensionSchema.extensionNumber(nextExtension) <= number) {
                extensionSchema.serializeExtension(writer, nextExtension);
                nextExtension = extensionIterator.hasNext() ? extensionIterator.next() : null;
            }

            switch (type(typeAndOffset)) {
                case 0: // DOUBLE:
                    if (isFieldPresent(message, pos)) {
                        writer.writeDouble(number, doubleAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 1: // FLOAT:
                    if (isFieldPresent(message, pos)) {
                        writer.writeFloat(number, floatAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 2: // INT64:
                    if (isFieldPresent(message, pos)) {
                        writer.writeInt64(number, longAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 3: // UINT64:
                    if (isFieldPresent(message, pos)) {
                        writer.writeUInt64(number, longAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 4: // INT32:
                    if (isFieldPresent(message, pos)) {
                        writer.writeInt32(number, intAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 5: // FIXED64:
                    if (isFieldPresent(message, pos)) {
                        writer.writeFixed64(number, longAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 6: // FIXED32:
                    if (isFieldPresent(message, pos)) {
                        writer.writeFixed32(number, intAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 7: // BOOL:
                    if (isFieldPresent(message, pos)) {
                        writer.writeBool(number, booleanAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 8: // STRING:
                    if (isFieldPresent(message, pos)) {
                        writeString(number, UnsafeUtil.getObject(message, offset(typeAndOffset)), writer);
                    }
                    break;
                case 9: // MESSAGE:
                    if (isFieldPresent(message, pos)) {
                        Object value = UnsafeUtil.getObject(message, offset(typeAndOffset));
                        writer.writeMessage(number, value, getMessageFieldSchema(pos));
                    }
                    break;
                case 10: // BYTES:
                    if (isFieldPresent(message, pos)) {
                        writer.writeBytes(
                                number, (ByteString) UnsafeUtil.getObject(message, offset(typeAndOffset)));
                    }
                    break;
                case 11: // UINT32:
                    if (isFieldPresent(message, pos)) {
                        writer.writeUInt32(number, intAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 12: // ENUM:
                    if (isFieldPresent(message, pos)) {
                        writer.writeEnum(number, intAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 13: // SFIXED32:
                    if (isFieldPresent(message, pos)) {
                        writer.writeSFixed32(number, intAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 14: // SFIXED64:
                    if (isFieldPresent(message, pos)) {
                        writer.writeSFixed64(number, longAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 15: // SINT32:
                    if (isFieldPresent(message, pos)) {
                        writer.writeSInt32(number, intAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 16: // SINT64:
                    if (isFieldPresent(message, pos)) {
                        writer.writeSInt64(number, longAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 17: // GROUP:
                    if (isFieldPresent(message, pos)) {
                        writer.writeGroup(
                                number,
                                UnsafeUtil.getObject(message, offset(typeAndOffset)),
                                getMessageFieldSchema(pos));
                    }
                    break;
                case 18: // DOUBLE_LIST:
                    SchemaUtil.writeDoubleList(
                            numberAt(pos),
                            (List<Double>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 19: // FLOAT_LIST:
                    SchemaUtil.writeFloatList(
                            numberAt(pos),
                            (List<Float>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 20: // INT64_LIST:
                    SchemaUtil.writeInt64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 21: // UINT64_LIST:
                    SchemaUtil.writeUInt64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 22: // INT32_LIST:
                    SchemaUtil.writeInt32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 23: // FIXED64_LIST:
                    SchemaUtil.writeFixed64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 24: // FIXED32_LIST:
                    SchemaUtil.writeFixed32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 25: // BOOL_LIST:
                    SchemaUtil.writeBoolList(
                            numberAt(pos),
                            (List<Boolean>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 26: // STRING_LIST:
                    SchemaUtil.writeStringList(
                            numberAt(pos),
                            (List<String>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer);
                    break;
                case 27: // MESSAGE_LIST:
                    SchemaUtil.writeMessageList(
                            numberAt(pos),
                            (List<?>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            getMessageFieldSchema(pos));
                    break;
                case 28: // BYTES_LIST:
                    SchemaUtil.writeBytesList(
                            numberAt(pos),
                            (List<ByteString>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer);
                    break;
                case 29: // UINT32_LIST:
                    SchemaUtil.writeUInt32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 30: // ENUM_LIST:
                    SchemaUtil.writeEnumList(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 31: // SFIXED32_LIST:
                    SchemaUtil.writeSFixed32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 32: // SFIXED64_LIST:
                    SchemaUtil.writeSFixed64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 33: // SINT32_LIST:
                    SchemaUtil.writeSInt32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 34: // SINT64_LIST:
                    SchemaUtil.writeSInt64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 35: // DOUBLE_LIST_PACKED:
                    // TODO(xiaofeng): Make use of cached field size to speed up serialization.
                    SchemaUtil.writeDoubleList(
                            numberAt(pos),
                            (List<Double>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 36: // FLOAT_LIST_PACKED:
                    SchemaUtil.writeFloatList(
                            numberAt(pos),
                            (List<Float>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 37: // INT64_LIST_PACKED:
                    SchemaUtil.writeInt64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 38: // UINT64_LIST_PACKED:
                    SchemaUtil.writeUInt64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 39: // INT32_LIST_PACKED:
                    SchemaUtil.writeInt32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 40: // FIXED64_LIST_PACKED:
                    SchemaUtil.writeFixed64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 41: // FIXED32_LIST_PACKED:
                    SchemaUtil.writeFixed32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);

                    break;
                case 42: // BOOL_LIST_PACKED:
                    SchemaUtil.writeBoolList(
                            numberAt(pos),
                            (List<Boolean>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 43: // UINT32_LIST_PACKED:
                    SchemaUtil.writeUInt32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 44: // ENUM_LIST_PACKED:
                    SchemaUtil.writeEnumList(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 45: // SFIXED32_LIST_PACKED:
                    SchemaUtil.writeSFixed32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 46: // SFIXED64_LIST_PACKED:
                    SchemaUtil.writeSFixed64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 47: // SINT32_LIST_PACKED:
                    SchemaUtil.writeSInt32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 48: // SINT64_LIST_PACKED:
                    SchemaUtil.writeSInt64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 49: // GROUP_LIST:
                    SchemaUtil.writeGroupList(
                            numberAt(pos),
                            (List<?>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            getMessageFieldSchema(pos));
                    break;
                case 50: // MAP:
                    // TODO(dweis): Use schema cache.
                    writeMapHelper(writer, number, UnsafeUtil.getObject(message, offset(typeAndOffset)), pos);
                    break;
                case 51: // ONEOF_DOUBLE:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeDouble(number, oneofDoubleAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 52: // ONEOF_FLOAT:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeFloat(number, oneofFloatAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 53: // ONEOF_INT64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeInt64(number, oneofLongAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 54: // ONEOF_UINT64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeUInt64(number, oneofLongAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 55: // ONEOF_INT32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeInt32(number, oneofIntAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 56: // ONEOF_FIXED64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeFixed64(number, oneofLongAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 57: // ONEOF_FIXED32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeFixed32(number, oneofIntAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 58: // ONEOF_BOOL:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeBool(number, oneofBooleanAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 59: // ONEOF_STRING:
                    if (isOneofPresent(message, number, pos)) {
                        writeString(number, UnsafeUtil.getObject(message, offset(typeAndOffset)), writer);
                    }
                    break;
                case 60: // ONEOF_MESSAGE:
                    if (isOneofPresent(message, number, pos)) {
                        Object value = UnsafeUtil.getObject(message, offset(typeAndOffset));
                        writer.writeMessage(number, value, getMessageFieldSchema(pos));
                    }
                    break;
                case 61: // ONEOF_BYTES:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeBytes(
                                number, (ByteString) UnsafeUtil.getObject(message, offset(typeAndOffset)));
                    }
                    break;
                case 62: // ONEOF_UINT32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeUInt32(number, oneofIntAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 63: // ONEOF_ENUM:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeEnum(number, oneofIntAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 64: // ONEOF_SFIXED32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeSFixed32(number, oneofIntAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 65: // ONEOF_SFIXED64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeSFixed64(number, oneofLongAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 66: // ONEOF_SINT32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeSInt32(number, oneofIntAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 67: // ONEOF_SINT64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeSInt64(number, oneofLongAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 68: // ONEOF_GROUP:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeGroup(
                                number,
                                UnsafeUtil.getObject(message, offset(typeAndOffset)),
                                getMessageFieldSchema(pos));
                    }
                    break;
                default:
                    // Assume it's an empty entry - just go to the next entry.
                    break;
            }
        }
        while (nextExtension != null) {
            extensionSchema.serializeExtension(writer, nextExtension);
            nextExtension = extensionIterator.hasNext() ? extensionIterator.next() : null;
        }
        writeUnknownInMessageTo(unknownFieldSchema, message, writer);
    }

    @SuppressWarnings("unchecked")
    private void writeFieldsInDescendingOrder(T message, Writer writer) throws IOException {
        writeUnknownInMessageTo(unknownFieldSchema, message, writer);

        Iterator<? extends Map.Entry<?, ?>> extensionIterator = null;
        Map.Entry nextExtension = null;
        if (hasExtensions) {
            FieldSet<?> extensions = extensionSchema.getExtensions(message);
            if (!extensions.isEmpty()) {
                extensionIterator = extensions.descendingIterator();
                nextExtension = extensionIterator.next();
            }
        }

        for (int pos = buffer.length - INTS_PER_FIELD; pos >= 0; pos -= INTS_PER_FIELD) {
            final int typeAndOffset = typeAndOffsetAt(pos);
            final int number = numberAt(pos);

            // Write any extensions that need to be written before the current field.
            while (nextExtension != null && extensionSchema.extensionNumber(nextExtension) > number) {
                extensionSchema.serializeExtension(writer, nextExtension);
                nextExtension = extensionIterator.hasNext() ? extensionIterator.next() : null;
            }

            switch (type(typeAndOffset)) {
                case 0: // DOUBLE:
                    if (isFieldPresent(message, pos)) {
                        writer.writeDouble(number, doubleAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 1: // FLOAT:
                    if (isFieldPresent(message, pos)) {
                        writer.writeFloat(number, floatAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 2: // INT64:
                    if (isFieldPresent(message, pos)) {
                        writer.writeInt64(number, longAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 3: // UINT64:
                    if (isFieldPresent(message, pos)) {
                        writer.writeUInt64(number, longAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 4: // INT32:
                    if (isFieldPresent(message, pos)) {
                        writer.writeInt32(number, intAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 5: // FIXED64:
                    if (isFieldPresent(message, pos)) {
                        writer.writeFixed64(number, longAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 6: // FIXED32:
                    if (isFieldPresent(message, pos)) {
                        writer.writeFixed32(number, intAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 7: // BOOL:
                    if (isFieldPresent(message, pos)) {
                        writer.writeBool(number, booleanAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 8: // STRING:
                    if (isFieldPresent(message, pos)) {
                        writeString(number, UnsafeUtil.getObject(message, offset(typeAndOffset)), writer);
                    }
                    break;
                case 9: // MESSAGE:
                    if (isFieldPresent(message, pos)) {
                        Object value = UnsafeUtil.getObject(message, offset(typeAndOffset));
                        writer.writeMessage(number, value, getMessageFieldSchema(pos));
                    }
                    break;
                case 10: // BYTES:
                    if (isFieldPresent(message, pos)) {
                        writer.writeBytes(
                                number, (ByteString) UnsafeUtil.getObject(message, offset(typeAndOffset)));
                    }
                    break;
                case 11: // UINT32:
                    if (isFieldPresent(message, pos)) {
                        writer.writeUInt32(number, intAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 12: // ENUM:
                    if (isFieldPresent(message, pos)) {
                        writer.writeEnum(number, intAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 13: // SFIXED32:
                    if (isFieldPresent(message, pos)) {
                        writer.writeSFixed32(number, intAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 14: // SFIXED64:
                    if (isFieldPresent(message, pos)) {
                        writer.writeSFixed64(number, longAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 15: // SINT32:
                    if (isFieldPresent(message, pos)) {
                        writer.writeSInt32(number, intAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 16: // SINT64:
                    if (isFieldPresent(message, pos)) {
                        writer.writeSInt64(number, longAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 17: // GROUP:
                    if (isFieldPresent(message, pos)) {
                        writer.writeGroup(
                                number,
                                UnsafeUtil.getObject(message, offset(typeAndOffset)),
                                getMessageFieldSchema(pos));
                    }
                    break;
                case 18: // DOUBLE_LIST:
                    SchemaUtil.writeDoubleList(
                            numberAt(pos),
                            (List<Double>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 19: // FLOAT_LIST:
                    SchemaUtil.writeFloatList(
                            numberAt(pos),
                            (List<Float>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 20: // INT64_LIST:
                    SchemaUtil.writeInt64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 21: // UINT64_LIST:
                    SchemaUtil.writeUInt64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 22: // INT32_LIST:
                    SchemaUtil.writeInt32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 23: // FIXED64_LIST:
                    SchemaUtil.writeFixed64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 24: // FIXED32_LIST:
                    SchemaUtil.writeFixed32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 25: // BOOL_LIST:
                    SchemaUtil.writeBoolList(
                            numberAt(pos),
                            (List<Boolean>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 26: // STRING_LIST:
                    SchemaUtil.writeStringList(
                            numberAt(pos),
                            (List<String>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer);
                    break;
                case 27: // MESSAGE_LIST:
                    SchemaUtil.writeMessageList(
                            numberAt(pos),
                            (List<?>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            getMessageFieldSchema(pos));
                    break;
                case 28: // BYTES_LIST:
                    SchemaUtil.writeBytesList(
                            numberAt(pos),
                            (List<ByteString>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer);
                    break;
                case 29: // UINT32_LIST:
                    SchemaUtil.writeUInt32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 30: // ENUM_LIST:
                    SchemaUtil.writeEnumList(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 31: // SFIXED32_LIST:
                    SchemaUtil.writeSFixed32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 32: // SFIXED64_LIST:
                    SchemaUtil.writeSFixed64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 33: // SINT32_LIST:
                    SchemaUtil.writeSInt32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 34: // SINT64_LIST:
                    SchemaUtil.writeSInt64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            false);
                    break;
                case 35: // DOUBLE_LIST_PACKED:
                    SchemaUtil.writeDoubleList(
                            numberAt(pos),
                            (List<Double>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 36: // FLOAT_LIST_PACKED:
                    SchemaUtil.writeFloatList(
                            numberAt(pos),
                            (List<Float>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 37: // INT64_LIST_PACKED:
                    SchemaUtil.writeInt64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 38: // UINT64_LIST_PACKED:
                    SchemaUtil.writeUInt64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 39: // INT32_LIST_PACKED:
                    SchemaUtil.writeInt32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 40: // FIXED64_LIST_PACKED:
                    SchemaUtil.writeFixed64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 41: // FIXED32_LIST_PACKED:
                    SchemaUtil.writeFixed32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);

                    break;
                case 42: // BOOL_LIST_PACKED:
                    SchemaUtil.writeBoolList(
                            numberAt(pos),
                            (List<Boolean>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 43: // UINT32_LIST_PACKED:
                    SchemaUtil.writeUInt32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 44: // ENUM_LIST_PACKED:
                    SchemaUtil.writeEnumList(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 45: // SFIXED32_LIST_PACKED:
                    SchemaUtil.writeSFixed32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 46: // SFIXED64_LIST_PACKED:
                    SchemaUtil.writeSFixed64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 47: // SINT32_LIST_PACKED:
                    SchemaUtil.writeSInt32List(
                            numberAt(pos),
                            (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 48: // SINT64_LIST_PACKED:
                    SchemaUtil.writeSInt64List(
                            numberAt(pos),
                            (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            true);
                    break;
                case 49: // GROUP_LIST:
                    SchemaUtil.writeGroupList(
                            numberAt(pos),
                            (List<?>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
                            writer,
                            getMessageFieldSchema(pos));
                    break;
                case 50: // MAP:
                    // TODO(dweis): Use schema cache.
                    writeMapHelper(writer, number, UnsafeUtil.getObject(message, offset(typeAndOffset)), pos);
                    break;
                case 51: // ONEOF_DOUBLE:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeDouble(number, oneofDoubleAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 52: // ONEOF_FLOAT:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeFloat(number, oneofFloatAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 53: // ONEOF_INT64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeInt64(number, oneofLongAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 54: // ONEOF_UINT64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeUInt64(number, oneofLongAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 55: // ONEOF_INT32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeInt32(number, oneofIntAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 56: // ONEOF_FIXED64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeFixed64(number, oneofLongAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 57: // ONEOF_FIXED32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeFixed32(number, oneofIntAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 58: // ONEOF_BOOL:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeBool(number, oneofBooleanAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 59: // ONEOF_STRING:
                    if (isOneofPresent(message, number, pos)) {
                        writeString(number, UnsafeUtil.getObject(message, offset(typeAndOffset)), writer);
                    }
                    break;
                case 60: // ONEOF_MESSAGE:
                    if (isOneofPresent(message, number, pos)) {
                        Object value = UnsafeUtil.getObject(message, offset(typeAndOffset));
                        writer.writeMessage(number, value, getMessageFieldSchema(pos));
                    }
                    break;
                case 61: // ONEOF_BYTES:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeBytes(
                                number, (ByteString) UnsafeUtil.getObject(message, offset(typeAndOffset)));
                    }
                    break;
                case 62: // ONEOF_UINT32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeUInt32(number, oneofIntAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 63: // ONEOF_ENUM:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeEnum(number, oneofIntAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 64: // ONEOF_SFIXED32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeSFixed32(number, oneofIntAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 65: // ONEOF_SFIXED64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeSFixed64(number, oneofLongAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 66: // ONEOF_SINT32:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeSInt32(number, oneofIntAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 67: // ONEOF_SINT64:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeSInt64(number, oneofLongAt(message, offset(typeAndOffset)));
                    }
                    break;
                case 68: // ONEOF_GROUP:
                    if (isOneofPresent(message, number, pos)) {
                        writer.writeGroup(
                                number,
                                UnsafeUtil.getObject(message, offset(typeAndOffset)),
                                getMessageFieldSchema(pos));
                    }
                    break;
                default:
                    break;
            }
        }
        while (nextExtension != null) {
            extensionSchema.serializeExtension(writer, nextExtension);
            nextExtension = extensionIterator.hasNext() ? extensionIterator.next() : null;
        }
    }

    @SuppressWarnings("unchecked")
    private <K, V> void writeMapHelper(Writer writer, int number, Object mapField, int pos)
            throws IOException {
        if (mapField != null) {
            writer.writeMap(
                    number,
                    (MapEntryLite.Metadata<K, V>) mapFieldSchema.forMapMetadata(getMapFieldDefaultEntry(pos)),
                    (Map<K, V>) mapFieldSchema.forMapData(mapField));
        }
    }

    private <UT, UB> void writeUnknownInMessageTo(
            UnknownFieldSchema<UT, UB> schema, T message, Writer writer) throws IOException {
        schema.writeTo(schema.getFromMessage(message), writer);
    }

    @Override
    public void mergeFrom(T message, Reader reader, ExtensionRegistryLite extensionRegistry)
            throws IOException {
        if (extensionRegistry == null) {
            throw new NullPointerException();
        }
        mergeFromHelper(unknownFieldSchema, extensionSchema, message, reader, extensionRegistry);
    }

    /**
     * A helper method for wildcard capture of {@code unknownFieldSchema}. See:
     * https://docs.oracle.com/javase/tutorial/java/generics/capture.html
     */
    private <UT, UB, ET extends FieldDescriptorLite<ET>> void mergeFromHelper(
            UnknownFieldSchema<UT, UB> unknownFieldSchema,
            ExtensionSchema<ET> extensionSchema,
            T message,
            Reader reader,
            ExtensionRegistryLite extensionRegistry)
            throws IOException {
        UB unknownFields = null;
        FieldSet<ET> extensions = null;
        try {
            while (true) {
                final int number = reader.getFieldNumber();
                final int pos = positionForFieldNumber(number);
                if (pos < 0) {
                    if (number == Reader.READ_DONE) {
                        return;
                    }
                    // Check if it's an extension.
                    Object extension =
                            !hasExtensions
                                    ? null
                                    : extensionSchema.findExtensionByNumber(
                                    extensionRegistry, defaultInstance, number);
                    if (extension != null) {
                        if (extensions == null) {
                            extensions = extensionSchema.getMutableExtensions(message);
                        }
                        unknownFields =
                                extensionSchema.parseExtension(
                                        reader,
                                        extension,
                                        extensionRegistry,
                                        extensions,
                                        unknownFields,
                                        unknownFieldSchema);
                        continue;
                    }
                    if (unknownFieldSchema.shouldDiscardUnknownFields(reader)) {
                        if (reader.skipField()) {
                            continue;
                        }
                    } else {
                        if (unknownFields == null) {
                            unknownFields = unknownFieldSchema.getBuilderFromMessage(message);
                        }
                        // Unknown field.
                        if (unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) {
                            continue;
                        }
                    }
                    // Done reading.
                    return;
                }
                final int typeAndOffset = typeAndOffsetAt(pos);

                try {
                    switch (type(typeAndOffset)) {
                        case 0: // DOUBLE:
                            UnsafeUtil.putDouble(message, offset(typeAndOffset), reader.readDouble());
                            setFieldPresent(message, pos);
                            break;
                        case 1: // FLOAT:
                            UnsafeUtil.putFloat(message, offset(typeAndOffset), reader.readFloat());
                            setFieldPresent(message, pos);
                            break;
                        case 2: // INT64:
                            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readInt64());
                            setFieldPresent(message, pos);
                            break;
                        case 3: // UINT64:
                            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readUInt64());
                            setFieldPresent(message, pos);
                            break;
                        case 4: // INT32:
                            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readInt32());
                            setFieldPresent(message, pos);
                            break;
                        case 5: // FIXED64:
                            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readFixed64());
                            setFieldPresent(message, pos);
                            break;
                        case 6: // FIXED32:
                            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readFixed32());
                            setFieldPresent(message, pos);
                            break;
                        case 7: // BOOL:
                            UnsafeUtil.putBoolean(message, offset(typeAndOffset), reader.readBool());
                            setFieldPresent(message, pos);
                            break;
                        case 8: // STRING:
                            readString(message, typeAndOffset, reader);
                            setFieldPresent(message, pos);
                            break;
                        case 9: { // MESSAGE:
                            if (isFieldPresent(message, pos)) {
                                Object mergedResult =
                                        Internal.mergeMessage(
                                                UnsafeUtil.getObject(message, offset(typeAndOffset)),
                                                reader.readMessageBySchemaWithCheck(
                                                        (Schema<T>) getMessageFieldSchema(pos), extensionRegistry));
                                UnsafeUtil.putObject(message, offset(typeAndOffset), mergedResult);
                            } else {
                                UnsafeUtil.putObject(
                                        message,
                                        offset(typeAndOffset),
                                        reader.readMessageBySchemaWithCheck(
                                                (Schema<T>) getMessageFieldSchema(pos), extensionRegistry));
                                setFieldPresent(message, pos);
                            }
                            break;
                        }
                        case 10: // BYTES:
                            UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
                            setFieldPresent(message, pos);
                            break;
                        case 11: // UINT32:
                            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readUInt32());
                            setFieldPresent(message, pos);
                            break;
                        case 12: // ENUM:
                        {
                            int enumValue = reader.readEnum();
                            EnumVerifier enumVerifier = getEnumFieldVerifier(pos);
                            if (enumVerifier == null || enumVerifier.isInRange(enumValue)) {
                                UnsafeUtil.putInt(message, offset(typeAndOffset), enumValue);
                                setFieldPresent(message, pos);
                            } else {
                                unknownFields =
                                        SchemaUtil.storeUnknownEnum(
                                                number, enumValue, unknownFields, unknownFieldSchema);
                            }
                            break;
                        }
                        case 13: // SFIXED32:
                            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSFixed32());
                            setFieldPresent(message, pos);
                            break;
                        case 14: // SFIXED64:
                            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSFixed64());
                            setFieldPresent(message, pos);
                            break;
                        case 15: // SINT32:
                            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSInt32());
                            setFieldPresent(message, pos);
                            break;
                        case 16: // SINT64:
                            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSInt64());
                            setFieldPresent(message, pos);
                            break;
                        case 17: { // GROUP:
                            if (isFieldPresent(message, pos)) {
                                Object mergedResult =
                                        Internal.mergeMessage(
                                                UnsafeUtil.getObject(message, offset(typeAndOffset)),
                                                reader.readGroupBySchemaWithCheck(
                                                        (Schema<T>) getMessageFieldSchema(pos), extensionRegistry));
                                UnsafeUtil.putObject(message, offset(typeAndOffset), mergedResult);
                            } else {
                                UnsafeUtil.putObject(
                                        message,
                                        offset(typeAndOffset),
                                        reader.readGroupBySchemaWithCheck(
                                                (Schema<T>) getMessageFieldSchema(pos), extensionRegistry));
                                setFieldPresent(message, pos);
                            }
                            break;
                        }
                        case 18: // DOUBLE_LIST:
                            reader.readDoubleList(
                                    listFieldSchema.<Double>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 19: // FLOAT_LIST:
                            reader.readFloatList(
                                    listFieldSchema.<Float>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 20: // INT64_LIST:
                            reader.readInt64List(
                                    listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 21: // UINT64_LIST:
                            reader.readUInt64List(
                                    listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 22: // INT32_LIST:
                            reader.readInt32List(
                                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 23: // FIXED64_LIST:
                            reader.readFixed64List(
                                    listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 24: // FIXED32_LIST:
                            reader.readFixed32List(
                                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 25: // BOOL_LIST:
                            reader.readBoolList(
                                    listFieldSchema.<Boolean>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 26: // STRING_LIST:
                            readStringList(message, typeAndOffset, reader);
                            break;
                        case 27: { // MESSAGE_LIST:
                            readMessageList(
                                    message,
                                    typeAndOffset,
                                    reader,
                                    (Schema<T>) getMessageFieldSchema(pos),
                                    extensionRegistry);
                            break;
                        }
                        case 28: // BYTES_LIST:
                            reader.readBytesList(
                                    listFieldSchema.<ByteString>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 29: // UINT32_LIST:
                            reader.readUInt32List(
                                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 30: // ENUM_LIST:
                        {
                            List<Integer> enumList =
                                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset));
                            reader.readEnumList(enumList);
                            unknownFields =
                                    SchemaUtil.filterUnknownEnumList(
                                            number,
                                            enumList,
                                            getEnumFieldVerifier(pos),
                                            unknownFields,
                                            unknownFieldSchema);
                            break;
                        }
                        case 31: // SFIXED32_LIST:
                            reader.readSFixed32List(
                                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 32: // SFIXED64_LIST:
                            reader.readSFixed64List(
                                    listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 33: // SINT32_LIST:
                            reader.readSInt32List(
                                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 34: // SINT64_LIST:
                            reader.readSInt64List(
                                    listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 35: // DOUBLE_LIST_PACKED:
                            reader.readDoubleList(
                                    listFieldSchema.<Double>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 36: // FLOAT_LIST_PACKED:
                            reader.readFloatList(
                                    listFieldSchema.<Float>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 37: // INT64_LIST_PACKED:
                            reader.readInt64List(
                                    listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 38: // UINT64_LIST_PACKED:
                            reader.readUInt64List(
                                    listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 39: // INT32_LIST_PACKED:
                            reader.readInt32List(
                                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 40: // FIXED64_LIST_PACKED:
                            reader.readFixed64List(
                                    listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 41: // FIXED32_LIST_PACKED:
                            reader.readFixed32List(
                                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 42: // BOOL_LIST_PACKED:
                            reader.readBoolList(
                                    listFieldSchema.<Boolean>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 43: // UINT32_LIST_PACKED:
                            reader.readUInt32List(
                                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 44: // ENUM_LIST_PACKED:
                        {
                            List<Integer> enumList =
                                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset));
                            reader.readEnumList(enumList);
                            unknownFields =
                                    SchemaUtil.filterUnknownEnumList(
                                            number,
                                            enumList,
                                            getEnumFieldVerifier(pos),
                                            unknownFields,
                                            unknownFieldSchema);
                            break;
                        }
                        case 45: // SFIXED32_LIST_PACKED:
                            reader.readSFixed32List(
                                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 46: // SFIXED64_LIST_PACKED:
                            reader.readSFixed64List(
                                    listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 47: // SINT32_LIST_PACKED:
                            reader.readSInt32List(
                                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 48: // SINT64_LIST_PACKED:
                            reader.readSInt64List(
                                    listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
                            break;
                        case 49: { // GROUP_LIST:
                            readGroupList(
                                    message,
                                    offset(typeAndOffset),
                                    reader,
                                    (Schema<T>) getMessageFieldSchema(pos),
                                    extensionRegistry);
                            break;
                        }
                        case 50: // MAP:
                            mergeMap(message, pos, getMapFieldDefaultEntry(pos), extensionRegistry, reader);
                            break;
                        case 51: // ONEOF_DOUBLE:
                            UnsafeUtil.putObject(
                                    message, offset(typeAndOffset), Double.valueOf(reader.readDouble()));
                            setOneofPresent(message, number, pos);
                            break;
                        case 52: // ONEOF_FLOAT:
                            UnsafeUtil.putObject(
                                    message, offset(typeAndOffset), Float.valueOf(reader.readFloat()));
                            setOneofPresent(message, number, pos);
                            break;
                        case 53: // ONEOF_INT64:
                            UnsafeUtil.putObject(
                                    message, offset(typeAndOffset), Long.valueOf(reader.readInt64()));
                            setOneofPresent(message, number, pos);
                            break;
                        case 54: // ONEOF_UINT64:
                            UnsafeUtil.putObject(
                                    message, offset(typeAndOffset), Long.valueOf(reader.readUInt64()));
                            setOneofPresent(message, number, pos);
                            break;
                        case 55: // ONEOF_INT32:
                            UnsafeUtil.putObject(
                                    message, offset(typeAndOffset), Integer.valueOf(reader.readInt32()));
                            setOneofPresent(message, number, pos);
                            break;
                        case 56: // ONEOF_FIXED64:
                            UnsafeUtil.putObject(
                                    message, offset(typeAndOffset), Long.valueOf(reader.readFixed64()));
                            setOneofPresent(message, number, pos);
                            break;
                        case 57: // ONEOF_FIXED32:
                            UnsafeUtil.putObject(
                                    message, offset(typeAndOffset), Integer.valueOf(reader.readFixed32()));
                            setOneofPresent(message, number, pos);
                            break;
                        case 58: // ONEOF_BOOL:
                            UnsafeUtil.putObject(
                                    message, offset(typeAndOffset), Boolean.valueOf(reader.readBool()));
                            setOneofPresent(message, number, pos);
                            break;
                        case 59: // ONEOF_STRING:
                            readString(message, typeAndOffset, reader);
                            setOneofPresent(message, number, pos);
                            break;
                        case 60: // ONEOF_MESSAGE:
                            if (isOneofPresent(message, number, pos)) {
                                Object mergedResult =
                                        Internal.mergeMessage(
                                                UnsafeUtil.getObject(message, offset(typeAndOffset)),
                                                reader.readMessageBySchemaWithCheck(
                                                        getMessageFieldSchema(pos), extensionRegistry));
                                UnsafeUtil.putObject(message, offset(typeAndOffset), mergedResult);
                            } else {
                                UnsafeUtil.putObject(
                                        message,
                                        offset(typeAndOffset),
                                        reader.readMessageBySchemaWithCheck(
                                                getMessageFieldSchema(pos), extensionRegistry));
                                setFieldPresent(message, pos);
                            }
                            setOneofPresent(message, number, pos);
                            break;
                        case 61: // ONEOF_BYTES:
                            UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
                            setOneofPresent(message, number, pos);
                            break;
                        case 62: // ONEOF_UINT32:
                            UnsafeUtil.putObject(
                                    message, offset(typeAndOffset), Integer.valueOf(reader.readUInt32()));
                            setOneofPresent(message, number, pos);
                            break;
                        case 63: // ONEOF_ENUM:
                        {
                            int enumValue = reader.readEnum();
                            EnumVerifier enumVerifier = getEnumFieldVerifier(pos);
                            if (enumVerifier == null || enumVerifier.isInRange(enumValue)) {
                                UnsafeUtil.putObject(message, offset(typeAndOffset), enumValue);
                                setOneofPresent(message, number, pos);
                            } else {
                                unknownFields =
                                        SchemaUtil.storeUnknownEnum(
                                                number, enumValue, unknownFields, unknownFieldSchema);
                            }
                            break;
                        }
                        case 64: // ONEOF_SFIXED32:
                            UnsafeUtil.putObject(
                                    message, offset(typeAndOffset), Integer.valueOf(reader.readSFixed32()));
                            setOneofPresent(message, number, pos);
                            break;
                        case 65: // ONEOF_SFIXED64:
                            UnsafeUtil.putObject(
                                    message, offset(typeAndOffset), Long.valueOf(reader.readSFixed64()));
                            setOneofPresent(message, number, pos);
                            break;
                        case 66: // ONEOF_SINT32:
                            UnsafeUtil.putObject(
                                    message, offset(typeAndOffset), Integer.valueOf(reader.readSInt32()));
                            setOneofPresent(message, number, pos);
                            break;
                        case 67: // ONEOF_SINT64:
                            UnsafeUtil.putObject(
                                    message, offset(typeAndOffset), Long.valueOf(reader.readSInt64()));
                            setOneofPresent(message, number, pos);
                            break;
                        case 68: // ONEOF_GROUP:
                            UnsafeUtil.putObject(
                                    message,
                                    offset(typeAndOffset),
                                    reader.readGroupBySchemaWithCheck(getMessageFieldSchema(pos), extensionRegistry));
                            setOneofPresent(message, number, pos);
                            break;
                        default:
                            // Assume we've landed on an empty entry. Treat it as an unknown field.
                            if (unknownFields == null) {
                                unknownFields = unknownFieldSchema.newBuilder();
                            }
                            if (!unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) {
                                return;
                            }
                            break;
                    }
                } catch (InvalidProtocolBufferException.InvalidWireTypeException e) {
                    // Treat fields with an invalid wire type as unknown fields
                    // (i.e. same as the default case).
                    if (unknownFieldSchema.shouldDiscardUnknownFields(reader)) {
                        if (!reader.skipField()) {
                            return;
                        }
                    } else {
                        if (unknownFields == null) {
                            unknownFields = unknownFieldSchema.getBuilderFromMessage(message);
                        }
                        if (!unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) {
                            return;
                        }
                    }
                }
            }
        } finally {
            for (int i = checkInitializedCount; i < repeatedFieldOffsetStart; i++) {
                unknownFields =
                        filterMapUnknownEnumValues(message, intArray[i], unknownFields, unknownFieldSchema);
            }
            if (unknownFields != null) {
                unknownFieldSchema.setBuilderToMessage(message, unknownFields);
            }
        }
    }

    @SuppressWarnings("ReferenceEquality")
    static UnknownFieldSetLite getMutableUnknownFields(Object message) {
        UnknownFieldSetLite unknownFields = ((GeneratedMessageLite) message).unknownFields;
        if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) {
            unknownFields = UnknownFieldSetLite.newInstance();
            ((GeneratedMessageLite) message).unknownFields = unknownFields;
        }
        return unknownFields;
    }

    /**
     * Decodes a map entry key or value. Stores result in registers.object1.
     */
    private int decodeMapEntryValue(
            byte[] data,
            int position,
            int limit,
            WireFormat.FieldType fieldType,
            Class<?> messageType,
            Registers registers)
            throws IOException {
        switch (fieldType) {
            case BOOL:
                position = decodeVarint64(data, position, registers);
                registers.object1 = registers.long1 != 0;
                break;
            case BYTES:
                position = decodeBytes(data, position, registers);
                break;
            case DOUBLE:
                registers.object1 = decodeDouble(data, position);
                position += 8;
                break;
            case FIXED32:
            case SFIXED32:
                registers.object1 = decodeFixed32(data, position);
                position += 4;
                break;
            case FIXED64:
            case SFIXED64:
                registers.object1 = decodeFixed64(data, position);
                position += 8;
                break;
            case FLOAT:
                registers.object1 = decodeFloat(data, position);
                position += 4;
                break;
            case ENUM:
            case INT32:
            case UINT32:
                position = decodeVarint32(data, position, registers);
                registers.object1 = registers.int1;
                break;
            case INT64:
            case UINT64:
                position = decodeVarint64(data, position, registers);
                registers.object1 = registers.long1;
                break;
            case MESSAGE:
                position =
                        decodeMessageField(
                                Protobuf.getInstance().schemaFor(messageType), data, position, limit, registers);
                break;
            case SINT32:
                position = decodeVarint32(data, position, registers);
                registers.object1 = CodedInputStream.decodeZigZag32(registers.int1);
                break;
            case SINT64:
                position = decodeVarint64(data, position, registers);
                registers.object1 = CodedInputStream.decodeZigZag64(registers.long1);
                break;
            case STRING:
                position = decodeStringRequireUtf8(data, position, registers);
                break;
            default:
                throw new RuntimeException("unsupported field type.");
        }
        return position;
    }

    /**
     * Decodes a map entry.
     */
    private <K, V> int decodeMapEntry(
            byte[] data,
            int position,
            int limit,
            MapEntryLite.Metadata<K, V> metadata,
            Map<K, V> target,
            Registers registers)
            throws IOException {
        position = decodeVarint32(data, position, registers);
        final int length = registers.int1;
        if (length < 0 || length > limit - position) {
            throw InvalidProtocolBufferException.truncatedMessage();
        }
        final int end = position + length;
        K key = metadata.defaultKey;
        V value = metadata.defaultValue;
        while (position < end) {
            int tag = data[position++];
            if (tag < 0) {
                position = decodeVarint32(tag, data, position, registers);
                tag = registers.int1;
            }
            final int fieldNumber = tag >>> 3;
            final int wireType = tag & 0x7;
            switch (fieldNumber) {
                case 1:
                    if (wireType == metadata.keyType.getWireType()) {
                        position =
                                decodeMapEntryValue(data, position, limit, metadata.keyType, null, registers);
                        key = (K) registers.object1;
                        continue;
                    }
                    break;
                case 2:
                    if (wireType == metadata.valueType.getWireType()) {
                        position =
                                decodeMapEntryValue(
                                        data,
                                        position,
                                        limit,
                                        metadata.valueType,
                                        metadata.defaultValue.getClass(),
                                        registers);
                        value = (V) registers.object1;
                        continue;
                    }
                    break;
                default:
                    break;
            }
            position = skipField(tag, data, position, limit, registers);
        }
        if (position != end) {
            throw InvalidProtocolBufferException.parseFailure();
        }
        target.put(key, value);
        return end;
    }

    @SuppressWarnings("ReferenceEquality")
    private int parseRepeatedField(
            T message,
            byte[] data,
            int position,
            int limit,
            int tag,
            int number,
            int wireType,
            int bufferPosition,
            long typeAndOffset,
            int fieldType,
            long fieldOffset,
            Registers registers)
            throws IOException {
        ProtobufList<?> list = (ProtobufList<?>) UNSAFE.getObject(message, fieldOffset);
        if (!list.isModifiable()) {
            final int size = list.size();
            list =
                    list.mutableCopyWithCapacity(
                            size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
            UNSAFE.putObject(message, fieldOffset, list);
        }
        switch (fieldType) {
            case 18: // DOUBLE_LIST:
            case 35: // DOUBLE_LIST_PACKED:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position = decodePackedDoubleList(data, position, list, registers);
                } else if (wireType == WireFormat.WIRETYPE_FIXED64) {
                    position = decodeDoubleList(tag, data, position, limit, list, registers);
                }
                break;
            case 19: // FLOAT_LIST:
            case 36: // FLOAT_LIST_PACKED:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position = decodePackedFloatList(data, position, list, registers);
                } else if (wireType == WireFormat.WIRETYPE_FIXED32) {
                    position = decodeFloatList(tag, data, position, limit, list, registers);
                }
                break;
            case 20: // INT64_LIST:
            case 21: // UINT64_LIST:
            case 37: // INT64_LIST_PACKED:
            case 38: // UINT64_LIST_PACKED:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position = decodePackedVarint64List(data, position, list, registers);
                } else if (wireType == WireFormat.WIRETYPE_VARINT) {
                    position = decodeVarint64List(tag, data, position, limit, list, registers);
                }
                break;
            case 22: // INT32_LIST:
            case 29: // UINT32_LIST:
            case 39: // INT32_LIST_PACKED:
            case 43: // UINT32_LIST_PACKED:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position = decodePackedVarint32List(data, position, list, registers);
                } else if (wireType == WireFormat.WIRETYPE_VARINT) {
                    position = decodeVarint32List(tag, data, position, limit, list, registers);
                }
                break;
            case 23: // FIXED64_LIST:
            case 32: // SFIXED64_LIST:
            case 40: // FIXED64_LIST_PACKED:
            case 46: // SFIXED64_LIST_PACKED:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position = decodePackedFixed64List(data, position, list, registers);
                } else if (wireType == WireFormat.WIRETYPE_FIXED64) {
                    position = decodeFixed64List(tag, data, position, limit, list, registers);
                }
                break;
            case 24: // FIXED32_LIST:
            case 31: // SFIXED32_LIST:
            case 41: // FIXED32_LIST_PACKED:
            case 45: // SFIXED32_LIST_PACKED:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position = decodePackedFixed32List(data, position, list, registers);
                } else if (wireType == WireFormat.WIRETYPE_FIXED32) {
                    position = decodeFixed32List(tag, data, position, limit, list, registers);
                }
                break;
            case 25: // BOOL_LIST:
            case 42: // BOOL_LIST_PACKED:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position = decodePackedBoolList(data, position, list, registers);
                } else if (wireType == WireFormat.WIRETYPE_VARINT) {
                    position = decodeBoolList(tag, data, position, limit, list, registers);
                }
                break;
            case 26: // STRING_LIST:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    if ((typeAndOffset & ENFORCE_UTF8_MASK) == 0) {
                        position = decodeStringList(tag, data, position, limit, list, registers);
                    } else {
                        position = decodeStringListRequireUtf8(tag, data, position, limit, list, registers);
                    }
                }
                break;
            case 27: // MESSAGE_LIST:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position =
                            decodeMessageList(
                                    getMessageFieldSchema(bufferPosition),
                                    tag,
                                    data,
                                    position,
                                    limit,
                                    list,
                                    registers);
                }
                break;
            case 28: // BYTES_LIST:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position = decodeBytesList(tag, data, position, limit, list, registers);
                }
                break;
            case 30: // ENUM_LIST:
            case 44: // ENUM_LIST_PACKED:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position = decodePackedVarint32List(data, position, list, registers);
                } else if (wireType == WireFormat.WIRETYPE_VARINT) {
                    position = decodeVarint32List(tag, data, position, limit, list, registers);
                } else {
                    break;
                }
                UnknownFieldSetLite unknownFields = ((GeneratedMessageLite) message).unknownFields;
                if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) {
                    // filterUnknownEnumList() expects the unknownFields parameter to be mutable or null.
                    // Since we don't know yet whether there exist unknown enum values, we'd better pass
                    // null to it instead of allocating a mutable instance. This is also needed to be
                    // consistent with the behavior of generated parser/builder.
                    unknownFields = null;
                }
                unknownFields =
                        SchemaUtil.filterUnknownEnumList(
                                number,
                                (ProtobufList<Integer>) list,
                                getEnumFieldVerifier(bufferPosition),
                                unknownFields,
                                (UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite>) unknownFieldSchema);
                if (unknownFields != null) {
                    ((GeneratedMessageLite) message).unknownFields = unknownFields;
                }
                break;
            case 33: // SINT32_LIST:
            case 47: // SINT32_LIST_PACKED:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position = decodePackedSInt32List(data, position, list, registers);
                } else if (wireType == WireFormat.WIRETYPE_VARINT) {
                    position = decodeSInt32List(tag, data, position, limit, list, registers);
                }
                break;
            case 34: // SINT64_LIST:
            case 48: // SINT64_LIST_PACKED:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position = decodePackedSInt64List(data, position, list, registers);
                } else if (wireType == WireFormat.WIRETYPE_VARINT) {
                    position = decodeSInt64List(tag, data, position, limit, list, registers);
                }
                break;
            case 49: // GROUP_LIST:
                if (wireType == WireFormat.WIRETYPE_START_GROUP) {
                    position =
                            decodeGroupList(
                                    getMessageFieldSchema(bufferPosition),
                                    tag,
                                    data,
                                    position,
                                    limit,
                                    list,
                                    registers);
                }
                break;
            default:
                break;
        }
        return position;
    }

    private <K, V> int parseMapField(
            T message,
            byte[] data,
            int position,
            int limit,
            int bufferPosition,
            long fieldOffset,
            Registers registers)
            throws IOException {
        final sun.misc.Unsafe unsafe = UNSAFE;
        Object mapDefaultEntry = getMapFieldDefaultEntry(bufferPosition);
        Object mapField = unsafe.getObject(message, fieldOffset);
        if (mapFieldSchema.isImmutable(mapField)) {
            Object oldMapField = mapField;
            mapField = mapFieldSchema.newMapField(mapDefaultEntry);
            mapFieldSchema.mergeFrom(mapField, oldMapField);
            unsafe.putObject(message, fieldOffset, mapField);
        }
        return decodeMapEntry(
                data,
                position,
                limit,
                (Metadata<K, V>) mapFieldSchema.forMapMetadata(mapDefaultEntry),
                (Map<K, V>) mapFieldSchema.forMutableMapData(mapField),
                registers);
    }

    private int parseOneofField(
            T message,
            byte[] data,
            int position,
            int limit,
            int tag,
            int number,
            int wireType,
            int typeAndOffset,
            int fieldType,
            long fieldOffset,
            int bufferPosition,
            Registers registers)
            throws IOException {
        final sun.misc.Unsafe unsafe = UNSAFE;
        final long oneofCaseOffset = buffer[bufferPosition + 2] & OFFSET_MASK;
        switch (fieldType) {
            case 51: // ONEOF_DOUBLE:
                if (wireType == WireFormat.WIRETYPE_FIXED64) {
                    unsafe.putObject(message, fieldOffset, decodeDouble(data, position));
                    position += 8;
                    unsafe.putInt(message, oneofCaseOffset, number);
                }
                break;
            case 52: // ONEOF_FLOAT:
                if (wireType == WireFormat.WIRETYPE_FIXED32) {
                    unsafe.putObject(message, fieldOffset, decodeFloat(data, position));
                    position += 4;
                    unsafe.putInt(message, oneofCaseOffset, number);
                }
                break;
            case 53: // ONEOF_INT64:
            case 54: // ONEOF_UINT64:
                if (wireType == WireFormat.WIRETYPE_VARINT) {
                    position = decodeVarint64(data, position, registers);
                    unsafe.putObject(message, fieldOffset, registers.long1);
                    unsafe.putInt(message, oneofCaseOffset, number);
                }
                break;
            case 55: // ONEOF_INT32:
            case 62: // ONEOF_UINT32:
                if (wireType == WireFormat.WIRETYPE_VARINT) {
                    position = decodeVarint32(data, position, registers);
                    unsafe.putObject(message, fieldOffset, registers.int1);
                    unsafe.putInt(message, oneofCaseOffset, number);
                }
                break;
            case 56: // ONEOF_FIXED64:
            case 65: // ONEOF_SFIXED64:
                if (wireType == WireFormat.WIRETYPE_FIXED64) {
                    unsafe.putObject(message, fieldOffset, decodeFixed64(data, position));
                    position += 8;
                    unsafe.putInt(message, oneofCaseOffset, number);
                }
                break;
            case 57: // ONEOF_FIXED32:
            case 64: // ONEOF_SFIXED32:
                if (wireType == WireFormat.WIRETYPE_FIXED32) {
                    unsafe.putObject(message, fieldOffset, decodeFixed32(data, position));
                    position += 4;
                    unsafe.putInt(message, oneofCaseOffset, number);
                }
                break;
            case 58: // ONEOF_BOOL:
                if (wireType == WireFormat.WIRETYPE_VARINT) {
                    position = decodeVarint64(data, position, registers);
                    unsafe.putObject(message, fieldOffset, registers.long1 != 0);
                    unsafe.putInt(message, oneofCaseOffset, number);
                }
                break;
            case 59: // ONEOF_STRING:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position = decodeVarint32(data, position, registers);
                    final int length = registers.int1;
                    if (length == 0) {
                        unsafe.putObject(message, fieldOffset, "");
                    } else {
                        if ((typeAndOffset & ENFORCE_UTF8_MASK) != 0
                                && !Utf8.isValidUtf8(data, position, position + length)) {
                            throw InvalidProtocolBufferException.invalidUtf8();
                        }
                        final String value = new String(data, position, length, StandardCharsets.UTF_8);
                        unsafe.putObject(message, fieldOffset, value);
                        position += length;
                    }
                    unsafe.putInt(message, oneofCaseOffset, number);
                }
                break;
            case 60: // ONEOF_MESSAGE:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position =
                            decodeMessageField(
                                    getMessageFieldSchema(bufferPosition), data, position, limit, registers);
                    final Object oldValue =
                            unsafe.getInt(message, oneofCaseOffset) == number
                                    ? unsafe.getObject(message, fieldOffset)
                                    : null;
                    if (oldValue == null) {
                        unsafe.putObject(message, fieldOffset, registers.object1);
                    } else {
                        unsafe.putObject(
                                message, fieldOffset, Internal.mergeMessage(oldValue, registers.object1));
                    }
                    unsafe.putInt(message, oneofCaseOffset, number);
                }
                break;
            case 61: // ONEOF_BYTES:
                if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                    position = decodeBytes(data, position, registers);
                    unsafe.putObject(message, fieldOffset, registers.object1);
                    unsafe.putInt(message, oneofCaseOffset, number);
                }
                break;
            case 63: // ONEOF_ENUM:
                if (wireType == WireFormat.WIRETYPE_VARINT) {
                    position = decodeVarint32(data, position, registers);
                    final int enumValue = registers.int1;
                    EnumVerifier enumVerifier = getEnumFieldVerifier(bufferPosition);
                    if (enumVerifier == null || enumVerifier.isInRange(enumValue)) {
                        unsafe.putObject(message, fieldOffset, enumValue);
                        unsafe.putInt(message, oneofCaseOffset, number);
                    } else {
                        // UnknownFieldSetLite requires varint to be represented as Long.
                        getMutableUnknownFields(message).storeField(tag, (long) enumValue);
                    }
                }
                break;
            case 66: // ONEOF_SINT32:
                if (wireType == WireFormat.WIRETYPE_VARINT) {
                    position = decodeVarint32(data, position, registers);
                    unsafe.putObject(message, fieldOffset, CodedInputStream.decodeZigZag32(registers.int1));
                    unsafe.putInt(message, oneofCaseOffset, number);
                }
                break;
            case 67: // ONEOF_SINT64:
                if (wireType == WireFormat.WIRETYPE_VARINT) {
                    position = decodeVarint64(data, position, registers);
                    unsafe.putObject(message, fieldOffset, CodedInputStream.decodeZigZag64(registers.long1));
                    unsafe.putInt(message, oneofCaseOffset, number);
                }
                break;
            case 68: // ONEOF_GROUP:
                if (wireType == WireFormat.WIRETYPE_START_GROUP) {
                    final int endTag = (tag & ~0x7) | WireFormat.WIRETYPE_END_GROUP;
                    position =
                            decodeGroupField(
                                    getMessageFieldSchema(bufferPosition), data, position, limit, endTag, registers);
                    final Object oldValue =
                            unsafe.getInt(message, oneofCaseOffset) == number
                                    ? unsafe.getObject(message, fieldOffset)
                                    : null;
                    if (oldValue == null) {
                        unsafe.putObject(message, fieldOffset, registers.object1);
                    } else {
                        unsafe.putObject(
                                message, fieldOffset, Internal.mergeMessage(oldValue, registers.object1));
                    }
                    unsafe.putInt(message, oneofCaseOffset, number);
                }
                break;
            default:
                break;
        }
        return position;
    }

    private Schema getMessageFieldSchema(int pos) {
        final int index = pos / INTS_PER_FIELD * 2;
        Schema schema = (Schema) objects[index];
        if (schema != null) {
            return schema;
        }
        schema = Protobuf.getInstance().schemaFor((Class) objects[index + 1]);
        objects[index] = schema;
        return schema;
    }

    private Object getMapFieldDefaultEntry(int pos) {
        return objects[pos / INTS_PER_FIELD * 2];
    }

    private EnumVerifier getEnumFieldVerifier(int pos) {
        return (EnumVerifier) objects[pos / INTS_PER_FIELD * 2 + 1];
    }

    /**
     * Parses a proto2 message or group and returns the position after the message/group. If it's
     * parsing a message (endGroup == 0), returns limit if parsing is successful; It it's parsing a
     * group (endGroup != 0), parsing ends when a tag == endGroup is encountered and the position
     * after that tag is returned.
     */
    int parseProto2Message(
            T message, byte[] data, int position, int limit, int endGroup, Registers registers)
            throws IOException {
        final sun.misc.Unsafe unsafe = UNSAFE;
        int currentPresenceFieldOffset = NO_PRESENCE_SENTINEL;
        int currentPresenceField = 0;
        int tag = 0;
        int oldNumber = -1;
        int pos = 0;
        while (position < limit) {
            tag = data[position++];
            if (tag < 0) {
                position = decodeVarint32(tag, data, position, registers);
                tag = registers.int1;
            }
            final int number = tag >>> 3;
            final int wireType = tag & 0x7;
            if (number > oldNumber) {
                pos = positionForFieldNumber(number, pos / INTS_PER_FIELD);
            } else {
                pos = positionForFieldNumber(number);
            }
            oldNumber = number;
            if (pos == -1) {
                // need to reset
                pos = 0;
            } else {
                final int typeAndOffset = buffer[pos + 1];
                final int fieldType = type(typeAndOffset);
                final long fieldOffset = offset(typeAndOffset);
                if (fieldType <= 17) {
                    // Proto2 optional fields have has-bits.
                    final int presenceMaskAndOffset = buffer[pos + 2];
                    final int presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
                    final int presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
                    // We cache the 32-bit has-bits integer value and only write it back when parsing a field
                    // using a different has-bits integer.
                    if (presenceFieldOffset != currentPresenceFieldOffset) {
                        if (currentPresenceFieldOffset != NO_PRESENCE_SENTINEL) {
                            unsafe.putInt(message, (long) currentPresenceFieldOffset, currentPresenceField);
                        }
                        currentPresenceFieldOffset = presenceFieldOffset;
                        currentPresenceField = unsafe.getInt(message, (long) presenceFieldOffset);
                    }
                    switch (fieldType) {
                        case 0: // DOUBLE
                            if (wireType == WireFormat.WIRETYPE_FIXED64) {
                                UnsafeUtil.putDouble(message, fieldOffset, decodeDouble(data, position));
                                position += 8;
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 1: // FLOAT
                            if (wireType == WireFormat.WIRETYPE_FIXED32) {
                                UnsafeUtil.putFloat(message, fieldOffset, decodeFloat(data, position));
                                position += 4;
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 2: // INT64
                        case 3: // UINT64
                            if (wireType == WireFormat.WIRETYPE_VARINT) {
                                position = decodeVarint64(data, position, registers);
                                unsafe.putLong(message, fieldOffset, registers.long1);
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 4: // INT32
                        case 11: // UINT32
                            if (wireType == WireFormat.WIRETYPE_VARINT) {
                                position = decodeVarint32(data, position, registers);
                                unsafe.putInt(message, fieldOffset, registers.int1);
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 5: // FIXED64
                        case 14: // SFIXED64
                            if (wireType == WireFormat.WIRETYPE_FIXED64) {
                                unsafe.putLong(message, fieldOffset, decodeFixed64(data, position));
                                position += 8;
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 6: // FIXED32
                        case 13: // SFIXED32
                            if (wireType == WireFormat.WIRETYPE_FIXED32) {
                                unsafe.putInt(message, fieldOffset, decodeFixed32(data, position));
                                position += 4;
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 7: // BOOL
                            if (wireType == WireFormat.WIRETYPE_VARINT) {
                                position = decodeVarint64(data, position, registers);
                                UnsafeUtil.putBoolean(message, fieldOffset, registers.long1 != 0);
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 8: // STRING
                            if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                                if ((typeAndOffset & ENFORCE_UTF8_MASK) == 0) {
                                    position = decodeString(data, position, registers);
                                } else {
                                    position = decodeStringRequireUtf8(data, position, registers);
                                }
                                unsafe.putObject(message, fieldOffset, registers.object1);
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 9: // MESSAGE
                            if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                                position =
                                        decodeMessageField(
                                                getMessageFieldSchema(pos), data, position, limit, registers);
                                if ((currentPresenceField & presenceMask) == 0) {
                                    unsafe.putObject(message, fieldOffset, registers.object1);
                                } else {
                                    unsafe.putObject(
                                            message,
                                            fieldOffset,
                                            Internal.mergeMessage(
                                                    unsafe.getObject(message, fieldOffset), registers.object1));
                                }
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 10: // BYTES
                            if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                                position = decodeBytes(data, position, registers);
                                unsafe.putObject(message, fieldOffset, registers.object1);
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 12: // ENUM
                            if (wireType == WireFormat.WIRETYPE_VARINT) {
                                position = decodeVarint32(data, position, registers);
                                final int enumValue = registers.int1;
                                EnumVerifier enumVerifier = getEnumFieldVerifier(pos);
                                if (enumVerifier == null || enumVerifier.isInRange(enumValue)) {
                                    unsafe.putInt(message, fieldOffset, enumValue);
                                    currentPresenceField |= presenceMask;
                                } else {
                                    // UnknownFieldSetLite requires varint to be represented as Long.
                                    getMutableUnknownFields(message).storeField(tag, (long) enumValue);
                                }
                                continue;
                            }
                            break;
                        case 15: // SINT32
                            if (wireType == WireFormat.WIRETYPE_VARINT) {
                                position = decodeVarint32(data, position, registers);
                                unsafe.putInt(
                                        message, fieldOffset, CodedInputStream.decodeZigZag32(registers.int1));
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 16: // SINT64
                            if (wireType == WireFormat.WIRETYPE_VARINT) {
                                position = decodeVarint64(data, position, registers);
                                unsafe.putLong(
                                        message, fieldOffset, CodedInputStream.decodeZigZag64(registers.long1));

                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 17: // GROUP
                            if (wireType == WireFormat.WIRETYPE_START_GROUP) {
                                final int endTag = (number << 3) | WireFormat.WIRETYPE_END_GROUP;
                                position =
                                        decodeGroupField(
                                                getMessageFieldSchema(pos), data, position, limit, endTag, registers);
                                if ((currentPresenceField & presenceMask) == 0) {
                                    unsafe.putObject(message, fieldOffset, registers.object1);
                                } else {
                                    unsafe.putObject(
                                            message,
                                            fieldOffset,
                                            Internal.mergeMessage(
                                                    unsafe.getObject(message, fieldOffset), registers.object1));
                                }

                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        default:
                            break;
                    }
                } else if (fieldType == 27) {
                    // Handle repeated message fields.
                    if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                        ProtobufList<?> list = (ProtobufList<?>) unsafe.getObject(message, fieldOffset);
                        if (!list.isModifiable()) {
                            final int size = list.size();
                            list =
                                    list.mutableCopyWithCapacity(
                                            size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
                            unsafe.putObject(message, fieldOffset, list);
                        }
                        position =
                                decodeMessageList(
                                        getMessageFieldSchema(pos), tag, data, position, limit, list, registers);
                        continue;
                    }
                } else if (fieldType <= 49) {
                    // Handle all other repeated fields.
                    final int oldPosition = position;
                    position =
                            parseRepeatedField(
                                    message,
                                    data,
                                    position,
                                    limit,
                                    tag,
                                    number,
                                    wireType,
                                    pos,
                                    typeAndOffset,
                                    fieldType,
                                    fieldOffset,
                                    registers);
                    if (position != oldPosition) {
                        continue;
                    }
                } else if (fieldType == 50) {
                    if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                        final int oldPosition = position;
                        position = parseMapField(message, data, position, limit, pos, fieldOffset, registers);
                        if (position != oldPosition) {
                            continue;
                        }
                    }
                } else {
                    final int oldPosition = position;
                    position =
                            parseOneofField(
                                    message,
                                    data,
                                    position,
                                    limit,
                                    tag,
                                    number,
                                    wireType,
                                    typeAndOffset,
                                    fieldType,
                                    fieldOffset,
                                    pos,
                                    registers);
                    if (position != oldPosition) {
                        continue;
                    }
                }
            }
            if (tag == endGroup && endGroup != 0) {
                break;
            }

            if (hasExtensions
                    && registers.extensionRegistry != ExtensionRegistryLite.getEmptyRegistry()) {
                position = decodeExtensionOrUnknownField(
                        tag, data, position, limit, message, defaultInstance,
                        (UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite>) unknownFieldSchema,
                        registers);
            } else {
                position = decodeUnknownField(
                        tag, data, position, limit, getMutableUnknownFields(message), registers);
            }
        }
        if (currentPresenceFieldOffset != NO_PRESENCE_SENTINEL) {
            unsafe.putInt(message, (long) currentPresenceFieldOffset, currentPresenceField);
        }
        UnknownFieldSetLite unknownFields = null;
        for (int i = checkInitializedCount; i < repeatedFieldOffsetStart; i++) {
            unknownFields =
                    filterMapUnknownEnumValues(
                            message,
                            intArray[i],
                            unknownFields,
                            (UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite>) unknownFieldSchema);
        }
        if (unknownFields != null) {
            ((UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite>) unknownFieldSchema)
                    .setBuilderToMessage(message, unknownFields);
        }
        if (endGroup == 0) {
            if (position != limit) {
                throw InvalidProtocolBufferException.parseFailure();
            }
        } else {
            if (position > limit || tag != endGroup) {
                throw InvalidProtocolBufferException.parseFailure();
            }
        }
        return position;
    }

    /**
     * Parses a proto3 message and returns the limit if parsing is successful.
     */
    private int parseProto3Message(
            T message, byte[] data, int position, int limit, Registers registers) throws IOException {
        final sun.misc.Unsafe unsafe = UNSAFE;
        int currentPresenceFieldOffset = NO_PRESENCE_SENTINEL;
        int currentPresenceField = 0;
        int tag = 0;
        int oldNumber = -1;
        int pos = 0;
        while (position < limit) {
            tag = data[position++];
            if (tag < 0) {
                position = decodeVarint32(tag, data, position, registers);
                tag = registers.int1;
            }
            final int number = tag >>> 3;
            final int wireType = tag & 0x7;
            if (number > oldNumber) {
                pos = positionForFieldNumber(number, pos / INTS_PER_FIELD);
            } else {
                pos = positionForFieldNumber(number);
            }
            oldNumber = number;
            if (pos == -1) {
                // need to reset
                pos = 0;
            } else {
                final int typeAndOffset = buffer[pos + 1];
                final int fieldType = type(typeAndOffset);
                final long fieldOffset = offset(typeAndOffset);
                if (fieldType <= 17) {
                    // Proto3 optional fields have has-bits.
                    final int presenceMaskAndOffset = buffer[pos + 2];
                    final int presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
                    final int presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
                    // We cache the 32-bit has-bits integer value and only write it back when parsing a field
                    // using a different has-bits integer.
                    //
                    // Note that for fields that do not have hasbits, we unconditionally write and discard
                    // the data.
                    if (presenceFieldOffset != currentPresenceFieldOffset) {
                        if (currentPresenceFieldOffset != NO_PRESENCE_SENTINEL) {
                            unsafe.putInt(message, (long) currentPresenceFieldOffset, currentPresenceField);
                        }
                        if (presenceFieldOffset != NO_PRESENCE_SENTINEL) {
                            currentPresenceField = unsafe.getInt(message, (long) presenceFieldOffset);
                        }
                        currentPresenceFieldOffset = presenceFieldOffset;
                    }
                    switch (fieldType) {
                        case 0: // DOUBLE:
                            if (wireType == WireFormat.WIRETYPE_FIXED64) {
                                UnsafeUtil.putDouble(message, fieldOffset, decodeDouble(data, position));
                                position += 8;
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 1: // FLOAT:
                            if (wireType == WireFormat.WIRETYPE_FIXED32) {
                                UnsafeUtil.putFloat(message, fieldOffset, decodeFloat(data, position));
                                position += 4;
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 2: // INT64:
                        case 3: // UINT64:
                            if (wireType == WireFormat.WIRETYPE_VARINT) {
                                position = decodeVarint64(data, position, registers);
                                unsafe.putLong(message, fieldOffset, registers.long1);
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 4: // INT32:
                        case 11: // UINT32:
                            if (wireType == WireFormat.WIRETYPE_VARINT) {
                                position = decodeVarint32(data, position, registers);
                                unsafe.putInt(message, fieldOffset, registers.int1);
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 5: // FIXED64:
                        case 14: // SFIXED64:
                            if (wireType == WireFormat.WIRETYPE_FIXED64) {
                                unsafe.putLong(message, fieldOffset, decodeFixed64(data, position));
                                position += 8;
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 6: // FIXED32:
                        case 13: // SFIXED32:
                            if (wireType == WireFormat.WIRETYPE_FIXED32) {
                                unsafe.putInt(message, fieldOffset, decodeFixed32(data, position));
                                position += 4;
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 7: // BOOL:
                            if (wireType == WireFormat.WIRETYPE_VARINT) {
                                position = decodeVarint64(data, position, registers);
                                UnsafeUtil.putBoolean(message, fieldOffset, registers.long1 != 0);
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 8: // STRING:
                            if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                                if ((typeAndOffset & ENFORCE_UTF8_MASK) == 0) {
                                    position = decodeString(data, position, registers);
                                } else {
                                    position = decodeStringRequireUtf8(data, position, registers);
                                }
                                unsafe.putObject(message, fieldOffset, registers.object1);
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 9: // MESSAGE:
                            if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                                position =
                                        decodeMessageField(
                                                getMessageFieldSchema(pos), data, position, limit, registers);
                                final Object oldValue = unsafe.getObject(message, fieldOffset);
                                if (oldValue == null) {
                                    unsafe.putObject(message, fieldOffset, registers.object1);
                                } else {
                                    unsafe.putObject(
                                            message, fieldOffset, Internal.mergeMessage(oldValue, registers.object1));
                                }
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 10: // BYTES:
                            if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                                position = decodeBytes(data, position, registers);
                                unsafe.putObject(message, fieldOffset, registers.object1);
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 12: // ENUM:
                            if (wireType == WireFormat.WIRETYPE_VARINT) {
                                position = decodeVarint32(data, position, registers);
                                unsafe.putInt(message, fieldOffset, registers.int1);
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 15: // SINT32:
                            if (wireType == WireFormat.WIRETYPE_VARINT) {
                                position = decodeVarint32(data, position, registers);
                                unsafe.putInt(
                                        message, fieldOffset, CodedInputStream.decodeZigZag32(registers.int1));
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        case 16: // SINT64:
                            if (wireType == WireFormat.WIRETYPE_VARINT) {
                                position = decodeVarint64(data, position, registers);
                                unsafe.putLong(
                                        message, fieldOffset, CodedInputStream.decodeZigZag64(registers.long1));
                                currentPresenceField |= presenceMask;
                                continue;
                            }
                            break;
                        default:
                            break;
                    }
                } else if (fieldType == 27) {
                    // Handle repeated message field.
                    if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                        ProtobufList<?> list = (ProtobufList<?>) unsafe.getObject(message, fieldOffset);
                        if (!list.isModifiable()) {
                            final int size = list.size();
                            list =
                                    list.mutableCopyWithCapacity(
                                            size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
                            unsafe.putObject(message, fieldOffset, list);
                        }
                        position =
                                decodeMessageList(
                                        getMessageFieldSchema(pos), tag, data, position, limit, list, registers);
                        continue;
                    }
                } else if (fieldType <= 49) {
                    // Handle all other repeated fields.
                    final int oldPosition = position;
                    position =
                            parseRepeatedField(
                                    message,
                                    data,
                                    position,
                                    limit,
                                    tag,
                                    number,
                                    wireType,
                                    pos,
                                    typeAndOffset,
                                    fieldType,
                                    fieldOffset,
                                    registers);
                    if (position != oldPosition) {
                        continue;
                    }
                } else if (fieldType == 50) {
                    if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                        final int oldPosition = position;
                        position = parseMapField(message, data, position, limit, pos, fieldOffset, registers);
                        if (position != oldPosition) {
                            continue;
                        }
                    }
                } else {
                    final int oldPosition = position;
                    position =
                            parseOneofField(
                                    message,
                                    data,
                                    position,
                                    limit,
                                    tag,
                                    number,
                                    wireType,
                                    typeAndOffset,
                                    fieldType,
                                    fieldOffset,
                                    pos,
                                    registers);
                    if (position != oldPosition) {
                        continue;
                    }
                }
            }
            position = decodeUnknownField(
                    tag, data, position, limit, getMutableUnknownFields(message), registers);
        }
        if (currentPresenceFieldOffset != NO_PRESENCE_SENTINEL) {
            unsafe.putInt(message, (long) currentPresenceFieldOffset, currentPresenceField);
        }
        if (position != limit) {
            throw InvalidProtocolBufferException.parseFailure();
        }
        return position;
    }

    @Override
    public void mergeFrom(T message, byte[] data, int position, int limit, Registers registers)
            throws IOException {
        if (proto3) {
            parseProto3Message(message, data, position, limit, registers);
        } else {
            parseProto2Message(message, data, position, limit, 0, registers);
        }
    }

    @Override
    public void makeImmutable(T message) {
        // Make all repeated/map fields immutable.
        for (int i = checkInitializedCount; i < repeatedFieldOffsetStart; i++) {
            long offset = offset(typeAndOffsetAt(intArray[i]));
            Object mapField = UnsafeUtil.getObject(message, offset);
            if (mapField == null) {
                continue;
            }
            UnsafeUtil.putObject(message, offset, mapFieldSchema.toImmutable(mapField));
        }
        final int length = intArray.length;
        for (int i = repeatedFieldOffsetStart; i < length; i++) {
            listFieldSchema.makeImmutableListAt(message, intArray[i]);
        }
        unknownFieldSchema.makeImmutable(message);
        if (hasExtensions) {
            extensionSchema.makeImmutable(message);
        }
    }

    @SuppressWarnings("unchecked")
    private final <K, V> void mergeMap(
            Object message,
            int pos,
            Object mapDefaultEntry,
            ExtensionRegistryLite extensionRegistry,
            Reader reader)
            throws IOException {
        long offset = offset(typeAndOffsetAt(pos));
        Object mapField = UnsafeUtil.getObject(message, offset);
        // TODO(xiaofeng): Consider creating separate implementations for full and lite. In lite
        // runtime map field will never be null but here we still need to check null because the
        // code is shared by both full and lite. It might be better if full/lite uses different
        // schema implementations.
        if (mapField == null) {
            mapField = mapFieldSchema.newMapField(mapDefaultEntry);
            UnsafeUtil.putObject(message, offset, mapField);
        } else if (mapFieldSchema.isImmutable(mapField)) {
            Object oldMapField = mapField;
            mapField = mapFieldSchema.newMapField(mapDefaultEntry);
            mapFieldSchema.mergeFrom(mapField, oldMapField);
            UnsafeUtil.putObject(message, offset, mapField);
        }
        reader.readMap(
                (Map<K, V>) mapFieldSchema.forMutableMapData(mapField),
                (Metadata<K, V>) mapFieldSchema.forMapMetadata(mapDefaultEntry),
                extensionRegistry);
    }

    private final <UT, UB> UB filterMapUnknownEnumValues(
            Object message, int pos, UB unknownFields, UnknownFieldSchema<UT, UB> unknownFieldSchema) {
        int fieldNumber = numberAt(pos);
        long offset = offset(typeAndOffsetAt(pos));
        Object mapField = UnsafeUtil.getObject(message, offset);
        if (mapField == null) {
            return unknownFields;
        }
        EnumVerifier enumVerifier = getEnumFieldVerifier(pos);
        if (enumVerifier == null) {
            return unknownFields;
        }
        Map<?, ?> mapData = mapFieldSchema.forMutableMapData(mapField);
        // Filter unknown enum values.
        unknownFields =
                filterUnknownEnumMap(
                        pos, fieldNumber, mapData, enumVerifier, unknownFields, unknownFieldSchema);
        return unknownFields;
    }

    @SuppressWarnings("unchecked")
    private final <K, V, UT, UB> UB filterUnknownEnumMap(
            int pos,
            int number,
            Map<K, V> mapData,
            EnumVerifier enumVerifier,
            UB unknownFields,
            UnknownFieldSchema<UT, UB> unknownFieldSchema) {
        Metadata<K, V> metadata =
                (Metadata<K, V>) mapFieldSchema.forMapMetadata(getMapFieldDefaultEntry(pos));
        for (Iterator<Map.Entry<K, V>> it = mapData.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<K, V> entry = it.next();
            if (!enumVerifier.isInRange((Integer) entry.getValue())) {
                if (unknownFields == null) {
                    unknownFields = unknownFieldSchema.newBuilder();
                }
                int entrySize =
                        MapEntryLite.computeSerializedSize(metadata, entry.getKey(), entry.getValue());
                CodedBuilder codedBuilder = ByteString.newCodedBuilder(entrySize);
                CodedOutputStream codedOutput = codedBuilder.getCodedOutput();
                try {
                    MapEntryLite.writeTo(codedOutput, metadata, entry.getKey(), entry.getValue());
                } catch (IOException e) {
                    // Writing to ByteString CodedOutputStream should not throw IOException.
                    throw new RuntimeException(e);
                }
                unknownFieldSchema.addLengthDelimited(unknownFields, number, codedBuilder.build());
                it.remove();
            }
        }
        return unknownFields;
    }

    @Override
    public final boolean isInitialized(T message) {
        int currentPresenceFieldOffset = NO_PRESENCE_SENTINEL;
        int currentPresenceField = 0;
        for (int i = 0; i < checkInitializedCount; i++) {
            final int pos = intArray[i];
            final int number = numberAt(pos);
            final int typeAndOffset = typeAndOffsetAt(pos);

            int presenceMaskAndOffset = buffer[pos + 2];
            final int presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
            int presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
            if (presenceFieldOffset != currentPresenceFieldOffset) {
                currentPresenceFieldOffset = presenceFieldOffset;
                if (currentPresenceFieldOffset != NO_PRESENCE_SENTINEL) {
                    currentPresenceField = UNSAFE.getInt(message, (long) presenceFieldOffset);
                }
            }

            if (isRequired(typeAndOffset)) {
                if (!isFieldPresent(
                        message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
                    return false;
                }
                // If a required message field is set but has no required fields of it's own, we still
                // proceed and check the message is initialized. It should be fairly cheap to check these
                // messages but is worth documenting.
            }
            // Check nested message and groups.
            switch (type(typeAndOffset)) {
                case 9: // MESSAGE
                case 17: // GROUP
                    if (isFieldPresent(
                            message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)
                            && !isInitialized(message, typeAndOffset, getMessageFieldSchema(pos))) {
                        return false;
                    }
                    break;
                case 27: // MESSAGE_LIST
                case 49: // GROUP_LIST
                    if (!isListInitialized(message, typeAndOffset, pos)) {
                        return false;
                    }
                    break;
                case 60: // ONEOF_MESSAGE
                case 68: // ONEOF_GROUP
                    if (isOneofPresent(message, number, pos)
                            && !isInitialized(message, typeAndOffset, getMessageFieldSchema(pos))) {
                        return false;
                    }
                    break;
                case 50: // MAP
                    if (!isMapInitialized(message, typeAndOffset, pos)) {
                        return false;
                    }
                    break;
                default:
                    break;
            }
        }

        if (hasExtensions) {
            if (!extensionSchema.getExtensions(message).isInitialized()) {
                return false;
            }
        }

        return true;
    }

    private static boolean isInitialized(Object message, int typeAndOffset, Schema schema) {
        Object nested = UnsafeUtil.getObject(message, offset(typeAndOffset));
        return schema.isInitialized(nested);
    }

    private <N> boolean isListInitialized(Object message, int typeAndOffset, int pos) {
        @SuppressWarnings("unchecked")
        List<N> list = (List<N>) UnsafeUtil.getObject(message, offset(typeAndOffset));
        if (list.isEmpty()) {
            return true;
        }

        Schema schema = getMessageFieldSchema(pos);
        for (int i = 0; i < list.size(); i++) {
            N nested = list.get(i);
            if (!schema.isInitialized(nested)) {
                return false;
            }
        }
        return true;
    }

    private boolean isMapInitialized(T message, int typeAndOffset, int pos) {
        Map<?, ?> map = mapFieldSchema.forMapData(UnsafeUtil.getObject(message, offset(typeAndOffset)));
        if (map.isEmpty()) {
            return true;
        }
        Object mapDefaultEntry = getMapFieldDefaultEntry(pos);
        MapEntryLite.Metadata<?, ?> metadata = mapFieldSchema.forMapMetadata(mapDefaultEntry);
        if (metadata.valueType.getJavaType() != WireFormat.JavaType.MESSAGE) {
            return true;
        }
        // TODO(dweis): Use schema cache.
        Schema schema = null;
        for (Object nested : map.values()) {
            if (schema == null) {
                schema = Protobuf.getInstance().schemaFor(nested.getClass());
            }
            if (!schema.isInitialized(nested)) {
                return false;
            }
        }
        return true;
    }

    private void writeString(int fieldNumber, Object value, Writer writer) throws IOException {
        if (value instanceof String) {
            writer.writeString(fieldNumber, (String) value);
        } else {
            writer.writeBytes(fieldNumber, (ByteString) value);
        }
    }

    private void readString(Object message, int typeAndOffset, Reader reader) throws IOException {
        if (isEnforceUtf8(typeAndOffset)) {
            // Enforce valid UTF-8 on the read.
            UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readStringRequireUtf8());
        } else if (lite) {
            // Lite messages use String fields to store strings. Read a string but do not
            // enforce UTF-8
            UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readString());
        } else {
            // Full runtime messages use Objects to store either a String or ByteString. Read
            // the string as a ByteString and do not enforce UTF-8.
            UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
        }
    }

    private void readStringList(Object message, int typeAndOffset, Reader reader) throws IOException {
        if (isEnforceUtf8(typeAndOffset)) {
            reader.readStringListRequireUtf8(
                    listFieldSchema.<String>mutableListAt(message, offset(typeAndOffset)));
        } else {
            reader.readStringList(listFieldSchema.<String>mutableListAt(message, offset(typeAndOffset)));
        }
    }

    private <E> void readMessageList(
            Object message,
            int typeAndOffset,
            Reader reader,
            Schema<E> schema,
            ExtensionRegistryLite extensionRegistry)
            throws IOException {
        long offset = offset(typeAndOffset);
        reader.readMessageList(
                listFieldSchema.<E>mutableListAt(message, offset), schema, extensionRegistry);
    }

    private <E> void readGroupList(
            Object message,
            long offset,
            Reader reader,
            Schema<E> schema,
            ExtensionRegistryLite extensionRegistry)
            throws IOException {
        reader.readGroupList(
                listFieldSchema.<E>mutableListAt(message, offset), schema, extensionRegistry);
    }

    private int numberAt(int pos) {
        return buffer[pos];
    }

    private int typeAndOffsetAt(int pos) {
        return buffer[pos + 1];
    }

    private int presenceMaskAndOffsetAt(int pos) {
        return buffer[pos + 2];
    }

    private static int type(int value) {
        return (value & FIELD_TYPE_MASK) >>> OFFSET_BITS;
    }

    private static boolean isRequired(int value) {
        return (value & REQUIRED_MASK) != 0;
    }

    private static boolean isEnforceUtf8(int value) {
        return (value & ENFORCE_UTF8_MASK) != 0;
    }

    private static long offset(int value) {
        return value & OFFSET_MASK;
    }

    private static <T> double doubleAt(T message, long offset) {
        return UnsafeUtil.getDouble(message, offset);
    }

    private static <T> float floatAt(T message, long offset) {
        return UnsafeUtil.getFloat(message, offset);
    }

    private static <T> int intAt(T message, long offset) {
        return UnsafeUtil.getInt(message, offset);
    }

    private static <T> long longAt(T message, long offset) {
        return UnsafeUtil.getLong(message, offset);
    }

    private static <T> boolean booleanAt(T message, long offset) {
        return UnsafeUtil.getBoolean(message, offset);
    }

    private static <T> double oneofDoubleAt(T message, long offset) {
        return ((Double) UnsafeUtil.getObject(message, offset)).doubleValue();
    }

    private static <T> float oneofFloatAt(T message, long offset) {
        return ((Float) UnsafeUtil.getObject(message, offset)).floatValue();
    }

    private static <T> int oneofIntAt(T message, long offset) {
        return ((Integer) UnsafeUtil.getObject(message, offset)).intValue();
    }

    private static <T> long oneofLongAt(T message, long offset) {
        return ((Long) UnsafeUtil.getObject(message, offset)).longValue();
    }

    private static <T> boolean oneofBooleanAt(T message, long offset) {
        return ((Boolean) UnsafeUtil.getObject(message, offset)).booleanValue();
    }

    /**
     * Returns true the field is present in both messages, or neither.
     */
    private boolean arePresentForEquals(T message, T other, int pos) {
        return isFieldPresent(message, pos) == isFieldPresent(other, pos);
    }

    private boolean isFieldPresent(
            T message, int pos, int presenceFieldOffset, int presenceField, int presenceMask) {
        if (presenceFieldOffset == NO_PRESENCE_SENTINEL) {
            return isFieldPresent(message, pos);
        } else {
            return (presenceField & presenceMask) != 0;
        }
    }

    private boolean isFieldPresent(T message, int pos) {
        final int presenceMaskAndOffset = presenceMaskAndOffsetAt(pos);
        final long presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
        if (presenceFieldOffset == NO_PRESENCE_SENTINEL) {
            final int typeAndOffset = typeAndOffsetAt(pos);
            final long offset = offset(typeAndOffset);
            switch (type(typeAndOffset)) {
                case 0: // DOUBLE:
                    return Double.doubleToRawLongBits(UnsafeUtil.getDouble(message, offset)) != 0L;
                case 1: // FLOAT:
                    return Float.floatToRawIntBits(UnsafeUtil.getFloat(message, offset)) != 0;
                case 2: // INT64:
                    return UnsafeUtil.getLong(message, offset) != 0L;
                case 3: // UINT64:
                    return UnsafeUtil.getLong(message, offset) != 0L;
                case 4: // INT32:
                    return UnsafeUtil.getInt(message, offset) != 0;
                case 5: // FIXED64:
                    return UnsafeUtil.getLong(message, offset) != 0L;
                case 6: // FIXED32:
                    return UnsafeUtil.getInt(message, offset) != 0;
                case 7: // BOOL:
                    return UnsafeUtil.getBoolean(message, offset);
                case 8: // STRING:
                    Object value = UnsafeUtil.getObject(message, offset);
                    if (value instanceof String) {
                        return !((String) value).isEmpty();
                    } else if (value instanceof ByteString) {
                        return !ByteString.EMPTY.equals(value);
                    } else {
                        throw new IllegalArgumentException();
                    }
                case 9: // MESSAGE:
                    return UnsafeUtil.getObject(message, offset) != null;
                case 10: // BYTES:
                    return !ByteString.EMPTY.equals(UnsafeUtil.getObject(message, offset));
                case 11: // UINT32:
                    return UnsafeUtil.getInt(message, offset) != 0;
                case 12: // ENUM:
                    return UnsafeUtil.getInt(message, offset) != 0;
                case 13: // SFIXED32:
                    return UnsafeUtil.getInt(message, offset) != 0;
                case 14: // SFIXED64:
                    return UnsafeUtil.getLong(message, offset) != 0L;
                case 15: // SINT32:
                    return UnsafeUtil.getInt(message, offset) != 0;
                case 16: // SINT64:
                    return UnsafeUtil.getLong(message, offset) != 0L;
                case 17: // GROUP:
                    return UnsafeUtil.getObject(message, offset) != null;
                default:
                    throw new IllegalArgumentException();
            }
        } else {
            final int presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
            return (UnsafeUtil.getInt(message, presenceMaskAndOffset & OFFSET_MASK) & presenceMask) != 0;
        }
    }

    private void setFieldPresent(T message, int pos) {
        int presenceMaskAndOffset = presenceMaskAndOffsetAt(pos);
        final long presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
        if (presenceFieldOffset == NO_PRESENCE_SENTINEL) {
            return;
        }
        final int presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
        UnsafeUtil.putInt(
                message,
                presenceFieldOffset,
                UnsafeUtil.getInt(message, presenceFieldOffset) | presenceMask);
    }

    private boolean isOneofPresent(T message, int fieldNumber, int pos) {
        int presenceMaskAndOffset = presenceMaskAndOffsetAt(pos);
        return UnsafeUtil.getInt(message, presenceMaskAndOffset & OFFSET_MASK) == fieldNumber;
    }

    private boolean isOneofCaseEqual(T message, T other, int pos) {
        int presenceMaskAndOffset = presenceMaskAndOffsetAt(pos);
        return UnsafeUtil.getInt(message, presenceMaskAndOffset & OFFSET_MASK)
                == UnsafeUtil.getInt(other, presenceMaskAndOffset & OFFSET_MASK);
    }

    private void setOneofPresent(T message, int fieldNumber, int pos) {
        int presenceMaskAndOffset = presenceMaskAndOffsetAt(pos);
        UnsafeUtil.putInt(message, presenceMaskAndOffset & OFFSET_MASK, fieldNumber);
    }

    private int positionForFieldNumber(final int number) {
        if (number >= minFieldNumber && number <= maxFieldNumber) {
            return slowPositionForFieldNumber(number, 0);
        }
        return -1;
    }

    private int positionForFieldNumber(final int number, final int min) {
        if (number >= minFieldNumber && number <= maxFieldNumber) {
            return slowPositionForFieldNumber(number, min);
        }
        return -1;
    }

    private int slowPositionForFieldNumber(final int number, int min) {
        int max = buffer.length / INTS_PER_FIELD - 1;
        while (min <= max) {
            // Find the midpoint address.
            final int mid = (max + min) >>> 1;
            final int pos = mid * INTS_PER_FIELD;
            final int midFieldNumber = numberAt(pos);
            if (number == midFieldNumber) {
                // Found the field.
                return pos;
            }
            if (number < midFieldNumber) {
                // Search the lower half.
                max = mid - 1;
            } else {
                // Search the upper half.
                min = mid + 1;
            }
        }
        return -1;
    }

    int getSchemaSize() {
        return buffer.length * 3;
    }
}
